diff --git a/src/Makefile.am b/src/Makefile.am index d58099445164c..4dfec82fd0583 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -96,9 +96,10 @@ BITCOIN_CORE_H = \ consensus/validation.h \ core_io.h \ core_memusage.h \ - darksend.h \ + privatesend-client.h \ + privatesend.h \ + privatesend-server.h \ dsnotificationinterface.h \ - darksend-relay.h \ governance.h \ governance-classes.h \ governance-exceptions.h \ @@ -220,6 +221,8 @@ libbitcoin_server_a_SOURCES = \ policy/fees.cpp \ policy/policy.cpp \ pow.cpp \ + privatesend.cpp \ + privatesend-server.cpp \ rest.cpp \ rpcblockchain.cpp \ rpcmasternode.cpp \ @@ -257,9 +260,8 @@ libbitcoin_wallet_a_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) libbitcoin_wallet_a_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS) libbitcoin_wallet_a_SOURCES = \ activemasternode.cpp \ - darksend.cpp \ + privatesend-client.cpp \ dsnotificationinterface.cpp \ - darksend-relay.cpp \ instantx.cpp \ masternode.cpp \ masternode-payments.cpp \ diff --git a/src/darksend.cpp b/src/darksend.cpp deleted file mode 100644 index b842c8352a3b8..0000000000000 --- a/src/darksend.cpp +++ /dev/null @@ -1,2473 +0,0 @@ -// 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 "activemasternode.h" -#include "coincontrol.h" -#include "consensus/validation.h" -#include "darksend.h" -#include "governance.h" -#include "init.h" -#include "instantx.h" -#include "masternode-payments.h" -#include "masternode-sync.h" -#include "masternodeman.h" -#include "messagesigner.h" -#include "script/sign.h" -#include "txmempool.h" -#include "util.h" -#include "utilmoneystr.h" - -#include - -int nPrivateSendRounds = DEFAULT_PRIVATESEND_ROUNDS; -int nPrivateSendAmount = DEFAULT_PRIVATESEND_AMOUNT; -int nLiquidityProvider = DEFAULT_PRIVATESEND_LIQUIDITY; -bool fEnablePrivateSend = false; -bool fPrivateSendMultiSession = DEFAULT_PRIVATESEND_MULTISESSION; - -CDarksendPool darkSendPool; -std::map mapDarksendBroadcastTxes; -std::vector vecPrivateSendDenominations; - -void CDarksendPool::ProcessMessage(CNode* pfrom, std::string& strCommand, CDataStream& vRecv) -{ - if(fLiteMode) return; // ignore all Dash related functionality - if(!masternodeSync.IsBlockchainSynced()) return; - - if(strCommand == NetMsgType::DSACCEPT) { - - if(pfrom->nVersion < MIN_PRIVATESEND_PEER_PROTO_VERSION) { - LogPrintf("DSACCEPT -- incompatible version! nVersion: %d\n", pfrom->nVersion); - PushStatus(pfrom, STATUS_REJECTED, ERR_VERSION); - return; - } - - if(!fMasterNode) { - LogPrintf("DSACCEPT -- not a Masternode!\n"); - PushStatus(pfrom, STATUS_REJECTED, ERR_NOT_A_MN); - return; - } - - if(IsSessionReady()) { - // too many users in this session already, reject new ones - LogPrintf("DSACCEPT -- queue is already full!\n"); - PushStatus(pfrom, STATUS_ACCEPTED, ERR_QUEUE_FULL); - return; - } - - int nDenom; - CTransaction txCollateral; - vRecv >> nDenom >> txCollateral; - - LogPrint("privatesend", "DSACCEPT -- nDenom %d (%s) txCollateral %s", nDenom, GetDenominationsToString(nDenom), txCollateral.ToString()); - - CMasternode* pmn = mnodeman.Find(activeMasternode.vin); - if(pmn == NULL) { - PushStatus(pfrom, STATUS_REJECTED, ERR_MN_LIST); - return; - } - - if(vecSessionCollaterals.size() == 0 && pmn->nLastDsq != 0 && - pmn->nLastDsq + mnodeman.CountEnabled(MIN_PRIVATESEND_PEER_PROTO_VERSION)/5 > mnodeman.nDsqCount) - { - LogPrintf("DSACCEPT -- last dsq too recent, must wait: addr=%s\n", pfrom->addr.ToString()); - PushStatus(pfrom, STATUS_REJECTED, ERR_RECENT); - return; - } - - PoolMessage nMessageID = MSG_NOERR; - - bool fResult = nSessionID == 0 ? CreateNewSession(nDenom, txCollateral, nMessageID) - : AddUserToExistingSession(nDenom, txCollateral, nMessageID); - if(fResult) { - LogPrintf("DSACCEPT -- is compatible, please submit!\n"); - PushStatus(pfrom, STATUS_ACCEPTED, nMessageID); - return; - } else { - LogPrintf("DSACCEPT -- not compatible with existing transactions!\n"); - PushStatus(pfrom, STATUS_REJECTED, nMessageID); - return; - } - - } else 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; - - CMasternode* pmn = mnodeman.Find(dsq.vin); - if(pmn == NULL) return; - - if(!dsq.CheckSignature(pmn->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(!pSubmittedToMasternode) return; - if((CNetAddr)pSubmittedToMasternode->addr != (CNetAddr)pmn->addr) { - LogPrintf("DSQUEUE -- message doesn't match current Masternode: pSubmittedToMasternode=%s, addr=%s\n", pSubmittedToMasternode->addr.ToString(), pmn->addr.ToString()); - return; - } - - if(nState == POOL_STATE_QUEUE) { - LogPrint("privatesend", "DSQUEUE -- PrivateSend queue (%s) is ready on masternode %s\n", dsq.ToString(), pmn->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", pmn->addr.ToString()); - return; - } - } - - int nThreshold = pmn->nLastDsq + mnodeman.CountEnabled(MIN_PRIVATESEND_PEER_PROTO_VERSION)/5; - LogPrint("privatesend", "DSQUEUE -- nLastDsq: %d threshold: %d nDsqCount: %d\n", pmn->nLastDsq, nThreshold, mnodeman.nDsqCount); - //don't allow a few nodes to dominate the queuing process - if(pmn->nLastDsq != 0 && nThreshold > mnodeman.nDsqCount) { - LogPrint("privatesend", "DSQUEUE -- Masternode %s is sending too many dsq messages\n", pmn->addr.ToString()); - return; - } - mnodeman.nDsqCount++; - pmn->nLastDsq = mnodeman.nDsqCount; - pmn->fAllowMixingTx = true; - - LogPrint("privatesend", "DSQUEUE -- new PrivateSend queue (%s) from masternode %s\n", dsq.ToString(), pmn->addr.ToString()); - if(pSubmittedToMasternode && pSubmittedToMasternode->vin.prevout == dsq.vin.prevout) { - dsq.fTried = true; - } - vecDarksendQueue.push_back(dsq); - dsq.Relay(); - } - - } else if(strCommand == NetMsgType::DSVIN) { - - if(pfrom->nVersion < MIN_PRIVATESEND_PEER_PROTO_VERSION) { - LogPrintf("DSVIN -- incompatible version! nVersion: %d\n", pfrom->nVersion); - PushStatus(pfrom, STATUS_REJECTED, ERR_VERSION); - return; - } - - if(!fMasterNode) { - LogPrintf("DSVIN -- not a Masternode!\n"); - PushStatus(pfrom, STATUS_REJECTED, ERR_NOT_A_MN); - return; - } - - //do we have enough users in the current session? - if(!IsSessionReady()) { - LogPrintf("DSVIN -- session not complete!\n"); - PushStatus(pfrom, STATUS_REJECTED, ERR_SESSION); - return; - } - - CDarkSendEntry entry; - vRecv >> entry; - - LogPrint("privatesend", "DSVIN -- txCollateral %s", entry.txCollateral.ToString()); - - if(entry.vecTxDSIn.size() > PRIVATESEND_ENTRY_MAX_SIZE) { - LogPrintf("DSVIN -- ERROR: too many inputs! %d/%d\n", entry.vecTxDSIn.size(), PRIVATESEND_ENTRY_MAX_SIZE); - PushStatus(pfrom, STATUS_REJECTED, ERR_MAXIMUM); - return; - } - - if(entry.vecTxDSOut.size() > PRIVATESEND_ENTRY_MAX_SIZE) { - LogPrintf("DSVIN -- ERROR: too many outputs! %d/%d\n", entry.vecTxDSOut.size(), PRIVATESEND_ENTRY_MAX_SIZE); - PushStatus(pfrom, STATUS_REJECTED, ERR_MAXIMUM); - return; - } - - //do we have the same denominations as the current session? - if(!IsOutputsCompatibleWithSessionDenom(entry.vecTxDSOut)) { - LogPrintf("DSVIN -- not compatible with existing transactions!\n"); - PushStatus(pfrom, STATUS_REJECTED, ERR_EXISTING_TX); - return; - } - - //check it like a transaction - { - CAmount nValueIn = 0; - CAmount nValueOut = 0; - - CMutableTransaction tx; - - BOOST_FOREACH(const CTxOut txout, entry.vecTxDSOut) { - nValueOut += txout.nValue; - tx.vout.push_back(txout); - - if(txout.scriptPubKey.size() != 25) { - LogPrintf("DSVIN -- non-standard pubkey detected! scriptPubKey=%s\n", ScriptToAsmStr(txout.scriptPubKey)); - PushStatus(pfrom, STATUS_REJECTED, ERR_NON_STANDARD_PUBKEY); - return; - } - if(!txout.scriptPubKey.IsNormalPaymentScript()) { - LogPrintf("DSVIN -- invalid script! scriptPubKey=%s\n", ScriptToAsmStr(txout.scriptPubKey)); - PushStatus(pfrom, STATUS_REJECTED, ERR_INVALID_SCRIPT); - return; - } - } - - BOOST_FOREACH(const CTxIn txin, entry.vecTxDSIn) { - tx.vin.push_back(txin); - - LogPrint("privatesend", "DSVIN -- txin=%s\n", txin.ToString()); - - CTransaction txPrev; - uint256 hash; - if(GetTransaction(txin.prevout.hash, txPrev, Params().GetConsensus(), hash, true)) { - if(txPrev.vout.size() > txin.prevout.n) - nValueIn += txPrev.vout[txin.prevout.n].nValue; - } else { - LogPrintf("DSVIN -- missing input! tx=%s", tx.ToString()); - PushStatus(pfrom, STATUS_REJECTED, ERR_MISSING_TX); - return; - } - } - - // There should be no fee in mixing tx - CAmount nFee = nValueIn - nValueOut; - if(nFee != 0) { - LogPrintf("DSVIN -- there should be no fee in mixing tx! fees: %lld, tx=%s", nFee, tx.ToString()); - PushStatus(pfrom, STATUS_REJECTED, ERR_FEES); - return; - } - - { - LOCK(cs_main); - CValidationState validationState; - mempool.PrioritiseTransaction(tx.GetHash(), tx.GetHash().ToString(), 1000, 0.1*COIN); - if(!AcceptToMemoryPool(mempool, validationState, CTransaction(tx), false, NULL, false, true, true)) { - LogPrintf("DSVIN -- transaction not valid! tx=%s", tx.ToString()); - PushStatus(pfrom, STATUS_REJECTED, ERR_INVALID_TX); - return; - } - } - } - - PoolMessage nMessageID = MSG_NOERR; - - if(AddEntry(entry, nMessageID)) { - PushStatus(pfrom, STATUS_ACCEPTED, nMessageID); - CheckPool(); - RelayStatus(STATUS_ACCEPTED); - } else { - PushStatus(pfrom, STATUS_REJECTED, nMessageID); - SetNull(); - } - - } else if(strCommand == NetMsgType::DSSTATUSUPDATE) { - - if(pfrom->nVersion < MIN_PRIVATESEND_PEER_PROTO_VERSION) { - LogPrintf("DSSTATUSUPDATE -- incompatible version! nVersion: %d\n", pfrom->nVersion); - return; - } - - if(fMasterNode) { - // LogPrintf("DSSTATUSUPDATE -- Can't run on a Masternode!\n"); - return; - } - - if(!pSubmittedToMasternode) return; - if((CNetAddr)pSubmittedToMasternode->addr != (CNetAddr)pfrom->addr) { - //LogPrintf("DSSTATUSUPDATE -- message doesn't match current Masternode: pSubmittedToMasternode %s addr %s\n", pSubmittedToMasternode->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::DSSIGNFINALTX) { - - if(pfrom->nVersion < MIN_PRIVATESEND_PEER_PROTO_VERSION) { - LogPrintf("DSSIGNFINALTX -- incompatible version! nVersion: %d\n", pfrom->nVersion); - return; - } - - if(!fMasterNode) { - LogPrintf("DSSIGNFINALTX -- not a Masternode!\n"); - return; - } - - std::vector vecTxIn; - vRecv >> vecTxIn; - - LogPrint("privatesend", "DSSIGNFINALTX -- vecTxIn.size() %s\n", vecTxIn.size()); - - int nTxInIndex = 0; - int nTxInsCount = (int)vecTxIn.size(); - - BOOST_FOREACH(const CTxIn txin, vecTxIn) { - nTxInIndex++; - if(!AddScriptSig(txin)) { - LogPrint("privatesend", "DSSIGNFINALTX -- AddScriptSig() failed at %d/%d, session: %d\n", nTxInIndex, nTxInsCount, nSessionID); - RelayStatus(STATUS_REJECTED); - return; - } - LogPrint("privatesend", "DSSIGNFINALTX -- AddScriptSig() %d/%d success\n", nTxInIndex, nTxInsCount); - } - // all is good - CheckPool(); - - } else if(strCommand == NetMsgType::DSFINALTX) { - - if(pfrom->nVersion < MIN_PRIVATESEND_PEER_PROTO_VERSION) { - LogPrintf("DSFINALTX -- incompatible version! nVersion: %d\n", pfrom->nVersion); - return; - } - - if(fMasterNode) { - // LogPrintf("DSFINALTX -- Can't run on a Masternode!\n"); - return; - } - - if(!pSubmittedToMasternode) return; - if((CNetAddr)pSubmittedToMasternode->addr != (CNetAddr)pfrom->addr) { - //LogPrintf("DSFINALTX -- message doesn't match current Masternode: pSubmittedToMasternode %s addr %s\n", pSubmittedToMasternode->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(fMasterNode) { - // LogPrintf("DSCOMPLETE -- Can't run on a Masternode!\n"); - return; - } - - if(!pSubmittedToMasternode) return; - if((CNetAddr)pSubmittedToMasternode->addr != (CNetAddr)pfrom->addr) { - LogPrint("privatesend", "DSCOMPLETE -- message doesn't match current Masternode: pSubmittedToMasternode=%s addr=%s\n", pSubmittedToMasternode->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", darkSendPool.nSessionID, nMsgSessionID); - return; - } - - LogPrint("privatesend", "DSCOMPLETE -- nMsgSessionID %d nMsgMessageID %d (%s)\n", nMsgSessionID, nMsgMessageID, GetMessageByID(PoolMessage(nMsgMessageID))); - - CompletedTransaction(PoolMessage(nMsgMessageID)); - } -} - -void CDarksendPool::InitDenominations() -{ - vecPrivateSendDenominations.clear(); - /* Denominations - - A note about convertability. Within mixing pools, each denomination - is convertable to another. - - For example: - 1DRK+1000 == (.1DRK+100)*10 - 10DRK+10000 == (1DRK+1000)*10 - */ - /* Disabled - vecPrivateSendDenominations.push_back( (100 * COIN)+100000 ); - */ - vecPrivateSendDenominations.push_back( (10 * COIN)+10000 ); - vecPrivateSendDenominations.push_back( (1 * COIN)+1000 ); - vecPrivateSendDenominations.push_back( (.1 * COIN)+100 ); - vecPrivateSendDenominations.push_back( (.01 * COIN)+10 ); - /* Disabled till we need them - vecPrivateSendDenominations.push_back( (.001 * COIN)+1 ); - */ -} - -void CDarksendPool::ResetPool() -{ - nCachedLastSuccessBlock = 0; - txMyCollateral = CMutableTransaction(); - vecMasternodesUsed.clear(); - UnlockCoins(); - SetNull(); -} - -void CDarksendPool::SetNull() -{ - // MN side - vecSessionCollaterals.clear(); - - // Client side - nEntriesCount = 0; - fLastEntryAccepted = false; - pSubmittedToMasternode = NULL; - - // Both sides - nState = POOL_STATE_IDLE; - nSessionID = 0; - nSessionDenom = 0; - vecEntries.clear(); - finalMutableTransaction.vin.clear(); - finalMutableTransaction.vout.clear(); - nTimeLastSuccessfulStep = GetTimeMillis(); -} - -// -// Unlock coins after mixing fails or succeeds -// -void CDarksendPool::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 CDarksendPool::GetStateString() const -{ - switch(nState) { - case POOL_STATE_IDLE: return "IDLE"; - case POOL_STATE_QUEUE: return "QUEUE"; - case POOL_STATE_ACCEPTING_ENTRIES: return "ACCEPTING_ENTRIES"; - case POOL_STATE_SIGNING: return "SIGNING"; - case POOL_STATE_ERROR: return "ERROR"; - case POOL_STATE_SUCCESS: return "SUCCESS"; - default: return "UNKNOWN"; - } -} - -std::string CDarksendPool::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 CDarksendPool::CheckPool() -{ - if(fMasterNode) { - LogPrint("privatesend", "CDarksendPool::CheckPool -- entries count %lu\n", GetEntriesCount()); - - // If entries are full, create finalized transaction - if(nState == POOL_STATE_ACCEPTING_ENTRIES && GetEntriesCount() >= GetMaxPoolTransactions()) { - LogPrint("privatesend", "CDarksendPool::CheckPool -- FINALIZE TRANSACTIONS\n"); - CreateFinalTransaction(); - return; - } - - // If we have all of the signatures, try to compile the transaction - if(nState == POOL_STATE_SIGNING && IsSignaturesComplete()) { - LogPrint("privatesend", "CDarksendPool::CheckPool -- SIGNING\n"); - CommitFinalTransaction(); - return; - } - } - - // reset if we're here for 10 seconds - if((nState == POOL_STATE_ERROR || nState == POOL_STATE_SUCCESS) && GetTimeMillis() - nTimeLastSuccessfulStep >= 10000) { - LogPrint("privatesend", "CDarksendPool::CheckPool -- timeout, RESETTING\n"); - UnlockCoins(); - SetNull(); - } -} - -void CDarksendPool::CreateFinalTransaction() -{ - LogPrint("privatesend", "CDarksendPool::CreateFinalTransaction -- FINALIZE TRANSACTIONS\n"); - - CMutableTransaction txNew; - - // make our new transaction - for(int i = 0; i < GetEntriesCount(); i++) { - BOOST_FOREACH(const CTxDSOut& txdsout, vecEntries[i].vecTxDSOut) - txNew.vout.push_back(txdsout); - - BOOST_FOREACH(const CTxDSIn& txdsin, vecEntries[i].vecTxDSIn) - txNew.vin.push_back(txdsin); - } - - // BIP69 https://github.com/kristovatlas/bips/blob/master/bip-0069.mediawiki - sort(txNew.vin.begin(), txNew.vin.end()); - sort(txNew.vout.begin(), txNew.vout.end()); - - finalMutableTransaction = txNew; - LogPrint("privatesend", "CDarksendPool::CreateFinalTransaction -- finalMutableTransaction=%s", txNew.ToString()); - - // request signatures from clients - RelayFinalTransaction(finalMutableTransaction); - SetState(POOL_STATE_SIGNING); -} - -void CDarksendPool::CommitFinalTransaction() -{ - if(!fMasterNode) return; // check and relay final tx only on masternode - - CTransaction finalTransaction = CTransaction(finalMutableTransaction); - uint256 hashTx = finalTransaction.GetHash(); - - LogPrint("privatesend", "CDarksendPool::CommitFinalTransaction -- finalTransaction=%s", finalTransaction.ToString()); - - { - // See if the transaction is valid - TRY_LOCK(cs_main, lockMain); - CValidationState validationState; - mempool.PrioritiseTransaction(hashTx, hashTx.ToString(), 1000, 0.1*COIN); - if(!lockMain || !AcceptToMemoryPool(mempool, validationState, finalTransaction, false, NULL, false, true, true)) - { - LogPrintf("CDarksendPool::CommitFinalTransaction -- AcceptToMemoryPool() error: Transaction not valid\n"); - SetNull(); - // not much we can do in this case, just notify clients - RelayCompletedTransaction(ERR_INVALID_TX); - return; - } - } - - LogPrintf("CDarksendPool::CommitFinalTransaction -- CREATING DSTX\n"); - - // create and sign masternode dstx transaction - if(!mapDarksendBroadcastTxes.count(hashTx)) { - CDarksendBroadcastTx dstx(finalTransaction, activeMasternode.vin, GetAdjustedTime()); - dstx.Sign(); - mapDarksendBroadcastTxes.insert(std::make_pair(hashTx, dstx)); - } - - LogPrintf("CDarksendPool::CommitFinalTransaction -- TRANSMITTING DSTX\n"); - - CInv inv(MSG_DSTX, hashTx); - RelayInv(inv); - - // Tell the clients it was successful - RelayCompletedTransaction(MSG_SUCCESS); - - // Randomly charge clients - ChargeRandomFees(); - - // Reset - LogPrint("privatesend", "CDarksendPool::CommitFinalTransaction -- COMPLETED -- RESETTING\n"); - SetNull(); -} - -// -// Charge clients a fee if they're abusive -// -// Why bother? PrivateSend uses collateral to ensure abuse to the process is kept to a minimum. -// The submission and signing stages are completely separate. In the cases where -// a client submits a transaction then refused to sign, there must be a cost. Otherwise they -// would be able to do this over and over again and bring the mixing to a hault. -// -// How does this work? Messages to Masternodes come in via NetMsgType::DSVIN, these require a valid collateral -// transaction for the client to be able to enter the pool. This transaction is kept by the Masternode -// until the transaction is either complete or fails. -// -void CDarksendPool::ChargeFees() -{ - if(!fMasterNode) return; - - //we don't need to charge collateral for every offence. - if(GetRandInt(100) > 33) return; - - std::vector vecOffendersCollaterals; - - if(nState == POOL_STATE_ACCEPTING_ENTRIES) { - BOOST_FOREACH(const CTransaction& txCollateral, vecSessionCollaterals) { - bool fFound = false; - BOOST_FOREACH(const CDarkSendEntry& entry, vecEntries) - if(entry.txCollateral == txCollateral) - fFound = true; - - // This queue entry didn't send us the promised transaction - if(!fFound) { - LogPrintf("CDarksendPool::ChargeFees -- found uncooperative node (didn't send transaction), found offence\n"); - vecOffendersCollaterals.push_back(txCollateral); - } - } - } - - if(nState == POOL_STATE_SIGNING) { - // who didn't sign? - BOOST_FOREACH(const CDarkSendEntry entry, vecEntries) { - BOOST_FOREACH(const CTxDSIn txdsin, entry.vecTxDSIn) { - if(!txdsin.fHasSig) { - LogPrintf("CDarksendPool::ChargeFees -- found uncooperative node (didn't sign), found offence\n"); - vecOffendersCollaterals.push_back(entry.txCollateral); - } - } - } - } - - // no offences found - if(vecOffendersCollaterals.empty()) return; - - //mostly offending? Charge sometimes - if((int)vecOffendersCollaterals.size() >= Params().PoolMaxTransactions() - 1 && GetRandInt(100) > 33) return; - - //everyone is an offender? That's not right - if((int)vecOffendersCollaterals.size() >= Params().PoolMaxTransactions()) return; - - //charge one of the offenders randomly - std::random_shuffle(vecOffendersCollaterals.begin(), vecOffendersCollaterals.end()); - - if(nState == POOL_STATE_ACCEPTING_ENTRIES || nState == POOL_STATE_SIGNING) { - LogPrintf("CDarksendPool::ChargeFees -- found uncooperative node (didn't %s transaction), charging fees: %s\n", - (nState == POOL_STATE_SIGNING) ? "sign" : "send", vecOffendersCollaterals[0].ToString()); - - LOCK(cs_main); - - CValidationState state; - bool fMissingInputs; - if(!AcceptToMemoryPool(mempool, state, vecOffendersCollaterals[0], false, &fMissingInputs, false, true)) { - // should never really happen - LogPrintf("CDarksendPool::ChargeFees -- ERROR: AcceptToMemoryPool failed!\n"); - } else { - RelayTransaction(vecOffendersCollaterals[0]); - } - } -} - -/* - Charge the collateral randomly. - Mixing is completely free, to pay miners we randomly pay the collateral of users. - - Collateral Fee Charges: - - Being that mixing has "no fees" we need to have some kind of cost associated - with using it to stop abuse. Otherwise it could serve as an attack vector and - allow endless transaction that would bloat Dash and make it unusable. To - stop these kinds of attacks 1 in 10 successful transactions are charged. This - adds up to a cost of 0.001DRK per transaction on average. -*/ -void CDarksendPool::ChargeRandomFees() -{ - if(!fMasterNode) return; - - LOCK(cs_main); - - BOOST_FOREACH(const CTransaction& txCollateral, vecSessionCollaterals) { - - if(GetRandInt(100) > 10) return; - - LogPrintf("CDarksendPool::ChargeRandomFees -- charging random fees, txCollateral=%s", txCollateral.ToString()); - - CValidationState state; - bool fMissingInputs; - if(!AcceptToMemoryPool(mempool, state, txCollateral, false, &fMissingInputs, false, true)) { - // should never really happen - LogPrintf("CDarksendPool::ChargeRandomFees -- ERROR: AcceptToMemoryPool failed!\n"); - } else { - RelayTransaction(txCollateral); - } - } -} - -// -// Check for various timeouts (queue objects, mixing, etc) -// -void CDarksendPool::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", "CDarksendPool::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", "CDarksendPool::CheckTimeout -- Pool error -- Running CheckPool\n"); - CheckPool(); - break; - case POOL_STATE_SUCCESS: - LogPrint("privatesend", "CDarksendPool::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", "CDarksendPool::CheckTimeout -- %s timed out (%ds) -- restting\n", - (nState == POOL_STATE_SIGNING) ? "Signing" : "Session", nTimeout); - ChargeFees(); - UnlockCoins(); - SetNull(); - SetState(POOL_STATE_ERROR); - strLastMessage = _("Session timed out."); - } -} - -/* - Check to see if we're ready for submissions from clients - After receiving multiple dsa messages, the queue will switch to "accepting entries" - which is the active state right before merging the transaction -*/ -void CDarksendPool::CheckForCompleteQueue() -{ - if(!fEnablePrivateSend && !fMasterNode) return; - - if(nState == POOL_STATE_QUEUE && IsSessionReady()) { - SetState(POOL_STATE_ACCEPTING_ENTRIES); - - CDarksendQueue dsq(nSessionDenom, activeMasternode.vin, GetTime(), true); - LogPrint("privatesend", "CDarksendPool::CheckForCompleteQueue -- queue is ready, signing and relaying (%s)\n", dsq.ToString()); - dsq.Sign(); - dsq.Relay(); - } -} - -// Check to make sure a given input matches an input in the pool and its scriptSig is valid -bool CDarksendPool::IsInputScriptSigValid(const CTxIn& txin) -{ - CMutableTransaction txNew; - txNew.vin.clear(); - txNew.vout.clear(); - - int i = 0; - int nTxInIndex = -1; - CScript sigPubKey = CScript(); - - BOOST_FOREACH(CDarkSendEntry& entry, vecEntries) { - - BOOST_FOREACH(const CTxDSOut& txdsout, entry.vecTxDSOut) - txNew.vout.push_back(txdsout); - - BOOST_FOREACH(const CTxDSIn& txdsin, entry.vecTxDSIn) { - txNew.vin.push_back(txdsin); - - if(txdsin.prevout == txin.prevout) { - nTxInIndex = i; - sigPubKey = txdsin.prevPubKey; - } - i++; - } - } - - if(nTxInIndex >= 0) { //might have to do this one input at a time? - txNew.vin[nTxInIndex].scriptSig = txin.scriptSig; - LogPrint("privatesend", "CDarksendPool::IsInputScriptSigValid -- verifying scriptSig %s\n", ScriptToAsmStr(txin.scriptSig).substr(0,24)); - if(!VerifyScript(txNew.vin[nTxInIndex].scriptSig, sigPubKey, SCRIPT_VERIFY_P2SH | SCRIPT_VERIFY_STRICTENC, MutableTransactionSignatureChecker(&txNew, nTxInIndex))) { - LogPrint("privatesend", "CDarksendPool::IsInputScriptSigValid -- VerifyScript() failed on input %d\n", nTxInIndex); - return false; - } - } else { - LogPrint("privatesend", "CDarksendPool::IsInputScriptSigValid -- Failed to find matching input in pool, %s\n", txin.ToString()); - return false; - } - - LogPrint("privatesend", "CDarksendPool::IsInputScriptSigValid -- Successfully validated input and scriptSig\n"); - return true; -} - -// check to make sure the collateral provided by the client is valid -bool CDarksendPool::IsCollateralValid(const CTransaction& txCollateral) -{ - if(txCollateral.vout.empty()) return false; - if(txCollateral.nLockTime != 0) return false; - - CAmount nValueIn = 0; - CAmount nValueOut = 0; - bool fMissingTx = false; - - BOOST_FOREACH(const CTxOut txout, txCollateral.vout) { - nValueOut += txout.nValue; - - if(!txout.scriptPubKey.IsNormalPaymentScript()) { - LogPrintf ("CDarksendPool::IsCollateralValid -- Invalid Script, txCollateral=%s", txCollateral.ToString()); - return false; - } - } - - BOOST_FOREACH(const CTxIn txin, txCollateral.vin) { - CTransaction txPrev; - uint256 hash; - if(GetTransaction(txin.prevout.hash, txPrev, Params().GetConsensus(), hash, true)) { - if(txPrev.vout.size() > txin.prevout.n) - nValueIn += txPrev.vout[txin.prevout.n].nValue; - } else { - fMissingTx = true; - } - } - - if(fMissingTx) { - LogPrint("privatesend", "CDarksendPool::IsCollateralValid -- Unknown inputs in collateral transaction, txCollateral=%s", txCollateral.ToString()); - return false; - } - - //collateral transactions are required to pay out PRIVATESEND_COLLATERAL as a fee to the miners - if(nValueIn - nValueOut < PRIVATESEND_COLLATERAL) { - LogPrint("privatesend", "CDarksendPool::IsCollateralValid -- did not include enough fees in transaction: fees: %d, txCollateral=%s", nValueOut - nValueIn, txCollateral.ToString()); - return false; - } - - LogPrint("privatesend", "CDarksendPool::IsCollateralValid -- %s", txCollateral.ToString()); - - { - LOCK(cs_main); - CValidationState validationState; - if(!AcceptToMemoryPool(mempool, validationState, txCollateral, false, NULL, false, true, true)) { - LogPrint("privatesend", "CDarksendPool::IsCollateralValid -- didn't pass AcceptToMemoryPool()\n"); - return false; - } - } - - return true; -} - - -// -// Add a clients transaction to the pool -// -bool CDarksendPool::AddEntry(const CDarkSendEntry& entryNew, PoolMessage& nMessageIDRet) -{ - if(!fMasterNode) return false; - - BOOST_FOREACH(CTxIn txin, entryNew.vecTxDSIn) { - if(txin.prevout.IsNull()) { - LogPrint("privatesend", "CDarksendPool::AddEntry -- input not valid!\n"); - nMessageIDRet = ERR_INVALID_INPUT; - return false; - } - } - - if(!IsCollateralValid(entryNew.txCollateral)) { - LogPrint("privatesend", "CDarksendPool::AddEntry -- collateral not valid!\n"); - nMessageIDRet = ERR_INVALID_COLLATERAL; - return false; - } - - if(GetEntriesCount() >= GetMaxPoolTransactions()) { - LogPrint("privatesend", "CDarksendPool::AddEntry -- entries is full!\n"); - nMessageIDRet = ERR_ENTRIES_FULL; - return false; - } - - BOOST_FOREACH(CTxIn txin, entryNew.vecTxDSIn) { - LogPrint("privatesend", "looking for txin -- %s\n", txin.ToString()); - BOOST_FOREACH(const CDarkSendEntry& entry, vecEntries) { - BOOST_FOREACH(const CTxDSIn& txdsin, entry.vecTxDSIn) { - if(txdsin.prevout == txin.prevout) { - LogPrint("privatesend", "CDarksendPool::AddEntry -- found in txin\n"); - nMessageIDRet = ERR_ALREADY_HAVE; - return false; - } - } - } - } - - vecEntries.push_back(entryNew); - - LogPrint("privatesend", "CDarksendPool::AddEntry -- adding entry\n"); - nMessageIDRet = MSG_ENTRIES_ADDED; - nTimeLastSuccessfulStep = GetTimeMillis(); - - return true; -} - -bool CDarksendPool::AddScriptSig(const CTxIn& txinNew) -{ - LogPrint("privatesend", "CDarksendPool::AddScriptSig -- scriptSig=%s\n", ScriptToAsmStr(txinNew.scriptSig).substr(0,24)); - - BOOST_FOREACH(const CDarkSendEntry& entry, vecEntries) { - BOOST_FOREACH(const CTxDSIn& txdsin, entry.vecTxDSIn) { - if(txdsin.scriptSig == txinNew.scriptSig) { - LogPrint("privatesend", "CDarksendPool::AddScriptSig -- already exists\n"); - return false; - } - } - } - - if(!IsInputScriptSigValid(txinNew)) { - LogPrint("privatesend", "CDarksendPool::AddScriptSig -- Invalid scriptSig\n"); - return false; - } - - LogPrint("privatesend", "CDarksendPool::AddScriptSig -- scriptSig=%s new\n", ScriptToAsmStr(txinNew.scriptSig).substr(0,24)); - - BOOST_FOREACH(CTxIn& txin, finalMutableTransaction.vin) { - if(txinNew.prevout == txin.prevout && txin.nSequence == txinNew.nSequence) { - txin.scriptSig = txinNew.scriptSig; - txin.prevPubKey = txinNew.prevPubKey; - LogPrint("privatesend", "CDarksendPool::AddScriptSig -- adding to finalMutableTransaction, scriptSig=%s\n", ScriptToAsmStr(txinNew.scriptSig).substr(0,24)); - } - } - for(int i = 0; i < GetEntriesCount(); i++) { - if(vecEntries[i].AddScriptSig(txinNew)) { - LogPrint("privatesend", "CDarksendPool::AddScriptSig -- adding to entries, scriptSig=%s\n", ScriptToAsmStr(txinNew.scriptSig).substr(0,24)); - return true; - } - } - - LogPrintf("CDarksendPool::AddScriptSig -- Couldn't set sig!\n" ); - return false; -} - -// Check to make sure everything is signed -bool CDarksendPool::IsSignaturesComplete() -{ - BOOST_FOREACH(const CDarkSendEntry& entry, vecEntries) - BOOST_FOREACH(const CTxDSIn& txdsin, entry.vecTxDSIn) - if(!txdsin.fHasSig) return false; - - return true; -} - -// -// Execute a mixing denomination via a Masternode. -// This is only ran from clients -// -bool CDarksendPool::SendDenominate(const std::vector& vecTxIn, const std::vector& vecTxOut) -{ - if(fMasterNode) { - LogPrintf("CDarksendPool::SendDenominate -- PrivateSend from a Masternode is not supported currently.\n"); - return false; - } - - if(txMyCollateral == CMutableTransaction()) { - LogPrintf("CDarksendPool: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("CDarksendPool::SendDenominate -- No Masternode has been selected yet.\n"); - UnlockCoins(); - SetNull(); - return false; - } - - if(!CheckDiskSpace()) { - UnlockCoins(); - SetNull(); - fEnablePrivateSend = false; - LogPrintf("CDarksendPool::SendDenominate -- Not enough disk space, disabling PrivateSend.\n"); - return false; - } - - SetState(POOL_STATE_ACCEPTING_ENTRIES); - strLastMessage = ""; - - LogPrintf("CDarksendPool::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", "CDarksendPool::SendDenominate -- txin=%s\n", txin.ToString()); - tx.vin.push_back(txin); - } - - BOOST_FOREACH(const CTxOut& txout, vecTxOut) { - LogPrint("privatesend", "CDarksendPool::SendDenominate -- txout=%s\n", txout.ToString()); - tx.vout.push_back(txout); - } - - LogPrintf("CDarksendPool::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("CDarksendPool::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 CDarksendPool::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("CDarksendPool::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("CDarksendPool::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("CDarksendPool::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 CDarksendPool::SignFinalTransaction(const CTransaction& finalTransactionNew, CNode* pnode) -{ - if(fMasterNode || pnode == NULL) return false; - - finalMutableTransaction = finalTransactionNew; - LogPrintf("CDarksendPool::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("CDarksendPool::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", "CDarksendPool::SignFinalTransaction -- Signing my input %i\n", nMyInputIndex); - if(!SignSignature(keystore, prevPubKey, finalMutableTransaction, nMyInputIndex, int(SIGHASH_ALL|SIGHASH_ANYONECANPAY))) { // changes scriptSig - LogPrint("privatesend", "CDarksendPool::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", "CDarksendPool::SignFinalTransaction -- nMyInputIndex: %d, sigs.size(): %d, scriptSig=%s\n", nMyInputIndex, (int)sigs.size(), ScriptToAsmStr(finalMutableTransaction.vin[nMyInputIndex].scriptSig)); - } - } - } - - if(sigs.empty()) { - LogPrintf("CDarksendPool::SignFinalTransaction -- can't sign anything!\n"); - UnlockCoins(); - SetNull(); - - return false; - } - - // push all of our signatures to the Masternode - LogPrintf("CDarksendPool::SignFinalTransaction -- pushing sigs to the masternode, finalMutableTransaction=%s", finalMutableTransaction.ToString()); - pnode->PushMessage(NetMsgType::DSSIGNFINALTX, sigs); - SetState(POOL_STATE_SIGNING); - nTimeLastSuccessfulStep = GetTimeMillis(); - - return true; -} - -void CDarksendPool::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", "CDarksendPool::NewBlock\n"); - - CheckTimeout(); -} - -// mixing transaction was completed (failed or successful) -void CDarksendPool::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); -} - -// -// Passively run mixing in the background to anonymize funds based on the given configuration. -// -bool CDarksendPool::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; - } - - switch(nWalletBackups) { - case 0: - LogPrint("privatesend", "CDarksendPool::DoAutomaticDenominating -- 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", "CDarksendPool::DoAutomaticDenominating -- 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", "CDarksendPool::DoAutomaticDenominating -- 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", "CDarksendPool::DoAutomaticDenominating -- 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", "CDarksendPool::DoAutomaticDenominating -- 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", "CDarksendPool::DoAutomaticDenominating -- 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("CDarksendPool::DoAutomaticDenominating -- WARNING! Something went wrong on automatic backup: %s\n", warningString); - } - if(!errorString.empty()) { - // Things are really broken - LogPrintf("CDarksendPool::DoAutomaticDenominating -- ERROR! Failed to create automatic backup: %s\n", errorString); - strAutoDenomResult = strprintf(_("ERROR! Failed to create automatic backup") + ": %s", errorString); - return false; - } - } - } else { - // Wait for someone else (e.g. GUI action) to create automatic backup for us - return false; - } - } - - LogPrint("privatesend", "CDarksendPool::DoAutomaticDenominating -- Keys left since latest backup: %d\n", pwalletMain->nKeysLeftSinceAutoBackup); - - 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("CDarksendPool::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", "CDarksendPool::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("CDarksendPool::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", "CDarksendPool::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("CDarksendPool::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("CDarksendPool::DoAutomaticDenominating -- create collateral error:%s\n", strReason); - return false; - } - } else { - if(!IsCollateralValid(txMyCollateral)) { - LogPrintf("CDarksendPool::DoAutomaticDenominating -- invalid collateral, recreating...\n"); - if(!pwalletMain->CreateCollateralTransaction(txMyCollateral, strReason)) { - LogPrintf("CDarksendPool::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) { - - // 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; - - CMasternode* pmn = mnodeman.Find(dsq.vin); - if(pmn == NULL) { - LogPrintf("CDarksendPool::DoAutomaticDenominating -- dsq masternode is not in masternode list, masternode=%s\n", dsq.vin.prevout.ToStringShort()); - continue; - } - - if(pmn->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", "CDarksendPool::DoAutomaticDenominating -- 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("CDarksendPool::DoAutomaticDenominating -- 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(pmn->addr); - if(pnodeFound) { - if(pnodeFound->fDisconnect) { - continue; - } else { - pnodeFound->AddRef(); - } - } - } - - LogPrintf("CDarksendPool::DoAutomaticDenominating -- attempt to connect to masternode from queue, addr=%s\n", pmn->addr.ToString()); - // connect to Masternode and submit the queue request - CNode* pnode = (pnodeFound && pnodeFound->fMasternode) ? pnodeFound : ConnectNode((CAddress)pmn->addr, NULL, true); - if(pnode) { - pSubmittedToMasternode = pmn; - nSessionDenom = dsq.nDenom; - - pnode->PushMessage(NetMsgType::DSACCEPT, nSessionDenom, txMyCollateral); - LogPrintf("CDarksendPool::DoAutomaticDenominating -- 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("CDarksendPool::DoAutomaticDenominating -- can't connect, addr=%s\n", pmn->addr.ToString()); - strAutoDenomResult = _("Error connecting to Masternode."); - continue; - } - } - } - - // do not initiate queue if we are a liquidity provider to avoid useless inter-mixing - if(nLiquidityProvider) return false; - - int nTries = 0; - - // ** 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("CDarksendPool::DoAutomaticDenominating -- 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) { - CMasternode* pmn = mnodeman.FindRandomNotInVec(vecMasternodesUsed, MIN_PRIVATESEND_PEER_PROTO_VERSION); - if(pmn == NULL) { - LogPrintf("CDarksendPool::DoAutomaticDenominating -- Can't find random masternode!\n"); - strAutoDenomResult = _("Can't find random Masternode."); - return false; - } - vecMasternodesUsed.push_back(pmn->vin); - - if(pmn->nLastDsq != 0 && pmn->nLastDsq + nMnCountEnabled/5 > mnodeman.nDsqCount) { - LogPrintf("CDarksendPool::DoAutomaticDenominating -- Too early to mix on this masternode!" - " masternode=%s addr=%s nLastDsq=%d CountEnabled/5=%d nDsqCount=%d\n", - pmn->vin.prevout.ToStringShort(), pmn->addr.ToString(), pmn->nLastDsq, - nMnCountEnabled/5, mnodeman.nDsqCount); - nTries++; - continue; - } - - CNode* pnodeFound = NULL; - { - LOCK(cs_vNodes); - pnodeFound = FindNode(pmn->addr); - if(pnodeFound) { - if(pnodeFound->fDisconnect) { - nTries++; - continue; - } else { - pnodeFound->AddRef(); - } - } - } - - LogPrintf("CDarksendPool::DoAutomaticDenominating -- attempt %d connection to Masternode %s\n", nTries, pmn->addr.ToString()); - CNode* pnode = (pnodeFound && pnodeFound->fMasternode) ? pnodeFound : ConnectNode((CAddress)pmn->addr, NULL, true); - if(pnode) { - LogPrintf("CDarksendPool::DoAutomaticDenominating -- connected, addr=%s\n", pmn->addr.ToString()); - pSubmittedToMasternode = pmn; - - 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("CDarksendPool::DoAutomaticDenominating -- 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("CDarksendPool::DoAutomaticDenominating -- can't connect, addr=%s\n", pmn->addr.ToString()); - nTries++; - continue; - } - } - - strAutoDenomResult = _("No compatible Masternode found."); - return false; -} - -bool CDarksendPool::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("CDarksendPool::SubmitDenominate -- Running PrivateSend denominate for %d rounds, success\n", i); - return SendDenominate(vecTxInRet, vecTxOutRet); - } - LogPrint("privatesend", "CDarksendPool::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("CDarksendPool::SubmitDenominate -- Running PrivateSend denominate for all rounds, success\n"); - return SendDenominate(vecTxInRet, vecTxOutRet); - } - - // Should never actually get here but just in case - LogPrintf("CDarksendPool::SubmitDenominate -- Running PrivateSend denominate for all rounds, error: %s\n", strError); - strAutoDenomResult = strError; - return false; -} - -bool CDarksendPool::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("CDarksendPool::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 scriptChange; - CPubKey vchPubKey; - // use a unique change address - assert(reservekey.GetReservedKey(vchPubKey)); // should never fail, as we just unlocked - scriptChange = GetScriptForDestination(vchPubKey.GetID()); - reservekey.KeepKey(); - - // add new output - CTxOut txout(nValueDenom, scriptChange); - 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 CDarksendPool::MakeCollateralAmounts() -{ - std::vector vecTally; - if(!pwalletMain->SelectCoinsGrouppedByAddresses(vecTally, false)) { - LogPrint("privatesend", "CDarksendPool::MakeCollateralAmounts -- SelectCoinsGrouppedByAddresses can't find any inputs!\n"); - return false; - } - - BOOST_FOREACH(CompactTallyItem& item, vecTally) { - if(!MakeCollateralAmounts(item)) continue; - return true; - } - - LogPrintf("CDarksendPool::MakeCollateralAmounts -- failed!\n"); - return false; -} - -// Split up large inputs or create fee sized inputs -bool CDarksendPool::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)); // 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("CDarksendPool::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("CDarksendPool::MakeCollateralAmounts -- ONLY_NOT1000IFMN Error: %s\n", strFail); - reservekeyCollateral.ReturnKey(); - return false; - } - } - - reservekeyCollateral.KeepKey(); - - LogPrintf("CDarksendPool::MakeCollateralAmounts -- txid=%s\n", wtx.GetHash().GetHex()); - - // use the same nCachedLastSuccessBlock as for DS mixinx to prevent race - if(!pwalletMain->CommitTransaction(wtx, reservekeyChange)) { - LogPrintf("CDarksendPool::MakeCollateralAmounts -- CommitTransaction failed!\n"); - return false; - } - - nCachedLastSuccessBlock = pCurrentBlockIndex->nHeight; - - return true; -} - -// Create denominations by looping through inputs grouped by addresses -bool CDarksendPool::CreateDenominated() -{ - std::vector vecTally; - if(!pwalletMain->SelectCoinsGrouppedByAddresses(vecTally)) { - LogPrint("privatesend", "CDarksendPool::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("CDarksendPool::CreateDenominated -- failed!\n"); - return false; -} - -// Create denominations -bool CDarksendPool::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)); // 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 - CReserveKey reservekeyDenom(pwalletMain); - - // 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("CDarksendPool::CreateDenominated -- %s\n", strAutoDenomResult); - vecDenominationsSkipped.push_back(nDenomValue); - continue; - } - } - - int nOutputs = 0; - - // add each output up to 10 times until it can't be added again - while(nValueLeft - nDenomValue >= 0 && nOutputs <= 10) { - CScript scriptDenom; - CPubKey vchPubKey; - //use a unique change address - assert(reservekeyDenom.GetReservedKey(vchPubKey)); // should never fail, as we just unlocked - scriptDenom = GetScriptForDestination(vchPubKey.GetID()); - // TODO: do not keep reservekeyDenom here - reservekeyDenom.KeepKey(); - - vecSend.push_back((CRecipient){ scriptDenom, nDenomValue, false }); - - //increment outputs and subtract denomination amount - nOutputs++; - nValueLeft -= nDenomValue; - LogPrintf("CreateDenominated1: nOutputsTotal: %d, nOutputs: %d, nValueLeft: %f\n", 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("CDarksendPool::CreateDenominated -- Error: %s\n", strFail); - // TODO: return reservekeyDenom here - reservekeyCollateral.ReturnKey(); - return false; - } - - // TODO: keep reservekeyDenom here - reservekeyCollateral.KeepKey(); - - if(!pwalletMain->CommitTransaction(wtx, reservekeyChange)) { - LogPrintf("CDarksendPool::CreateDenominated -- CommitTransaction failed!\n"); - return false; - } - - // use the same nCachedLastSuccessBlock as for DS mixing to prevent race - nCachedLastSuccessBlock = pCurrentBlockIndex->nHeight; - LogPrintf("CDarksendPool::CreateDenominated -- txid=%s\n", wtx.GetHash().GetHex()); - - return true; -} - -bool CDarksendPool::IsOutputsCompatibleWithSessionDenom(const std::vector& vecTxDSOut) -{ - if(GetDenominations(vecTxDSOut) == 0) return false; - - BOOST_FOREACH(const CDarkSendEntry entry, vecEntries) { - LogPrintf("CDarksendPool::IsOutputsCompatibleWithSessionDenom -- vecTxDSOut denom %d, entry.vecTxDSOut denom %d\n", GetDenominations(vecTxDSOut), GetDenominations(entry.vecTxDSOut)); - if(GetDenominations(vecTxDSOut) != GetDenominations(entry.vecTxDSOut)) return false; - } - - return true; -} - -bool CDarksendPool::IsAcceptableDenomAndCollateral(int nDenom, CTransaction txCollateral, PoolMessage& nMessageIDRet) -{ - if(!fMasterNode) return false; - - // is denom even smth legit? - std::vector vecBits; - if(!GetDenominationsBits(nDenom, vecBits)) { - LogPrint("privatesend", "CDarksendPool::IsAcceptableDenomAndCollateral -- denom not valid!\n"); - nMessageIDRet = ERR_DENOM; - return false; - } - - // check collateral - if(!fUnitTest && !IsCollateralValid(txCollateral)) { - LogPrint("privatesend", "CDarksendPool::IsAcceptableDenomAndCollateral -- collateral not valid!\n"); - nMessageIDRet = ERR_INVALID_COLLATERAL; - return false; - } - - return true; -} - -bool CDarksendPool::CreateNewSession(int nDenom, CTransaction txCollateral, PoolMessage& nMessageIDRet) -{ - if(!fMasterNode || nSessionID != 0) return false; - - // new session can only be started in idle mode - if(nState != POOL_STATE_IDLE) { - nMessageIDRet = ERR_MODE; - LogPrintf("CDarksendPool::CreateNewSession -- incompatible mode: nState=%d\n", nState); - return false; - } - - if(!IsAcceptableDenomAndCollateral(nDenom, txCollateral, nMessageIDRet)) { - return false; - } - - // start new session - nMessageIDRet = MSG_NOERR; - nSessionID = GetRandInt(999999)+1; - nSessionDenom = nDenom; - - SetState(POOL_STATE_QUEUE); - nTimeLastSuccessfulStep = GetTimeMillis(); - - if(!fUnitTest) { - //broadcast that I'm accepting entries, only if it's the first entry through - CDarksendQueue dsq(nDenom, activeMasternode.vin, GetTime(), false); - LogPrint("privatesend", "CDarksendPool::CreateNewSession -- signing and relaying new queue: %s\n", dsq.ToString()); - dsq.Sign(); - dsq.Relay(); - vecDarksendQueue.push_back(dsq); - } - - vecSessionCollaterals.push_back(txCollateral); - LogPrintf("CDarksendPool::CreateNewSession -- new session created, nSessionID: %d nSessionDenom: %d (%s) vecSessionCollaterals.size(): %d\n", - nSessionID, nSessionDenom, GetDenominationsToString(nSessionDenom), vecSessionCollaterals.size()); - - return true; -} - -bool CDarksendPool::AddUserToExistingSession(int nDenom, CTransaction txCollateral, PoolMessage& nMessageIDRet) -{ - if(!fMasterNode || nSessionID == 0 || IsSessionReady()) return false; - - if(!IsAcceptableDenomAndCollateral(nDenom, txCollateral, nMessageIDRet)) { - return false; - } - - // we only add new users to an existing session when we are in queue mode - if(nState != POOL_STATE_QUEUE) { - nMessageIDRet = ERR_MODE; - LogPrintf("CDarksendPool::AddUserToExistingSession -- incompatible mode: nState=%d\n", nState); - return false; - } - - if(nDenom != nSessionDenom) { - LogPrintf("CDarksendPool::AddUserToExistingSession -- incompatible denom %d (%s) != nSessionDenom %d (%s)\n", - nDenom, GetDenominationsToString(nDenom), nSessionDenom, GetDenominationsToString(nSessionDenom)); - nMessageIDRet = ERR_DENOM; - return false; - } - - // count new user as accepted to an existing session - - nMessageIDRet = MSG_NOERR; - nTimeLastSuccessfulStep = GetTimeMillis(); - vecSessionCollaterals.push_back(txCollateral); - - LogPrintf("CDarksendPool::AddUserToExistingSession -- new user accepted, nSessionID: %d nSessionDenom: %d (%s) vecSessionCollaterals.size(): %d\n", - nSessionID, nSessionDenom, GetDenominationsToString(nSessionDenom), vecSessionCollaterals.size()); - - return true; -} - -/* Create a nice string to show the denominations - Function returns as follows (for 4 denominations): - ( bit on if present ) - bit 0 - 100 - bit 1 - 10 - bit 2 - 1 - bit 3 - .1 - bit 4 and so on - out-of-bounds - none of above - non-denom -*/ -std::string CDarksendPool::GetDenominationsToString(int nDenom) -{ - std::string strDenom = ""; - int nMaxDenoms = vecPrivateSendDenominations.size(); - - if(nDenom >= (1 << nMaxDenoms)) { - return "out-of-bounds"; - } - - for (int i = 0; i < nMaxDenoms; ++i) { - if(nDenom & (1 << i)) { - strDenom += (strDenom.empty() ? "" : "+") + FormatMoney(vecPrivateSendDenominations[i]); - } - } - - if(strDenom.empty()) { - return "non-denom"; - } - - return strDenom; -} - -int CDarksendPool::GetDenominations(const std::vector& vecTxDSOut) -{ - std::vector vecTxOut; - - BOOST_FOREACH(CTxDSOut out, vecTxDSOut) - vecTxOut.push_back(out); - - return GetDenominations(vecTxOut); -} - -/* Return a bitshifted integer representing the denominations in this list - Function returns as follows (for 4 denominations): - ( bit on if present ) - 100 - bit 0 - 10 - bit 1 - 1 - bit 2 - .1 - bit 3 - non-denom - 0, all bits off -*/ -int CDarksendPool::GetDenominations(const std::vector& vecTxOut, bool fSingleRandomDenom) -{ - std::vector > vecDenomUsed; - - // make a list of denominations, with zero uses - BOOST_FOREACH(CAmount nDenomValue, vecPrivateSendDenominations) - vecDenomUsed.push_back(std::make_pair(nDenomValue, 0)); - - // look for denominations and update uses to 1 - BOOST_FOREACH(CTxOut txout, vecTxOut) { - bool found = false; - BOOST_FOREACH (PAIRTYPE(CAmount, int)& s, vecDenomUsed) { - if(txout.nValue == s.first) { - s.second = 1; - found = true; - } - } - if(!found) return 0; - } - - int nDenom = 0; - int c = 0; - // if the denomination is used, shift the bit on - BOOST_FOREACH (PAIRTYPE(CAmount, int)& s, vecDenomUsed) { - int bit = (fSingleRandomDenom ? GetRandInt(2) : 1) & s.second; - nDenom |= bit << c++; - if(fSingleRandomDenom && bit) break; // use just one random denomination - } - - return nDenom; -} - -bool CDarksendPool::GetDenominationsBits(int nDenom, std::vector &vecBitsRet) -{ - // ( bit on if present, 4 denominations example ) - // bit 0 - 100DASH+1 - // bit 1 - 10DASH+1 - // bit 2 - 1DASH+1 - // bit 3 - .1DASH+1 - - int nMaxDenoms = vecPrivateSendDenominations.size(); - - if(nDenom >= (1 << nMaxDenoms)) return false; - - vecBitsRet.clear(); - - for (int i = 0; i < nMaxDenoms; ++i) { - if(nDenom & (1 << i)) { - vecBitsRet.push_back(i); - } - } - - return !vecBitsRet.empty(); -} - -int CDarksendPool::GetDenominationsByAmounts(const std::vector& vecAmount) -{ - CScript scriptTmp = CScript(); - std::vector vecTxOut; - - BOOST_REVERSE_FOREACH(CAmount nAmount, vecAmount) { - CTxOut txout(nAmount, scriptTmp); - vecTxOut.push_back(txout); - } - - return GetDenominations(vecTxOut, true); -} - -std::string CDarksendPool::GetMessageByID(PoolMessage nMessageID) -{ - switch (nMessageID) { - case ERR_ALREADY_HAVE: return _("Already have that input."); - case ERR_DENOM: return _("No matching denominations found for mixing."); - case ERR_ENTRIES_FULL: return _("Entries are full."); - case ERR_EXISTING_TX: return _("Not compatible with existing transactions."); - case ERR_FEES: return _("Transaction fees are too high."); - case ERR_INVALID_COLLATERAL: return _("Collateral not valid."); - case ERR_INVALID_INPUT: return _("Input is not valid."); - case ERR_INVALID_SCRIPT: return _("Invalid script detected."); - case ERR_INVALID_TX: return _("Transaction not valid."); - case ERR_MAXIMUM: return _("Entry exceeds maximum size."); - case ERR_MN_LIST: return _("Not in the Masternode list."); - case ERR_MODE: return _("Incompatible mode."); - case ERR_NON_STANDARD_PUBKEY: return _("Non-standard public key detected."); - case ERR_NOT_A_MN: return _("This is not a Masternode."); - case ERR_QUEUE_FULL: return _("Masternode queue is full."); - case ERR_RECENT: return _("Last PrivateSend was too recent."); - case ERR_SESSION: return _("Session not complete!"); - case ERR_MISSING_TX: return _("Missing input transaction information."); - case ERR_VERSION: return _("Incompatible version."); - case MSG_NOERR: return _("No errors detected."); - case MSG_SUCCESS: return _("Transaction created successfully."); - case MSG_ENTRIES_ADDED: return _("Your entries added successfully."); - default: return _("Unknown response."); - } -} - -bool CDarkSendEntry::AddScriptSig(const CTxIn& txin) -{ - BOOST_FOREACH(CTxDSIn& txdsin, vecTxDSIn) { - if(txdsin.prevout == txin.prevout && txdsin.nSequence == txin.nSequence) { - if(txdsin.fHasSig) return false; - - txdsin.scriptSig = txin.scriptSig; - txdsin.prevPubKey = txin.prevPubKey; - txdsin.fHasSig = true; - - return true; - } - } - - return false; -} - -bool CDarksendQueue::Sign() -{ - if(!fMasterNode) return false; - - std::string strMessage = vin.ToString() + boost::lexical_cast(nDenom) + boost::lexical_cast(nTime) + boost::lexical_cast(fReady); - - if(!CMessageSigner::SignMessage(strMessage, vchSig, activeMasternode.keyMasternode)) { - LogPrintf("CDarksendQueue::Sign -- SignMessage() failed, %s\n", ToString()); - return false; - } - - return CheckSignature(activeMasternode.pubKeyMasternode); -} - -bool CDarksendQueue::CheckSignature(const CPubKey& pubKeyMasternode) -{ - std::string strMessage = vin.ToString() + boost::lexical_cast(nDenom) + boost::lexical_cast(nTime) + boost::lexical_cast(fReady); - std::string strError = ""; - - if(!CMessageSigner::VerifyMessage(pubKeyMasternode, vchSig, strMessage, strError)) { - LogPrintf("CDarksendQueue::CheckSignature -- Got bad Masternode queue signature: %s; error: %s\n", ToString(), strError); - return false; - } - - return true; -} - -bool CDarksendQueue::Relay() -{ - std::vector vNodesCopy = CopyNodeVector(); - BOOST_FOREACH(CNode* pnode, vNodesCopy) - if(pnode->nVersion >= MIN_PRIVATESEND_PEER_PROTO_VERSION) - pnode->PushMessage(NetMsgType::DSQUEUE, (*this)); - - ReleaseNodeVector(vNodesCopy); - return true; -} - -bool CDarksendBroadcastTx::Sign() -{ - if(!fMasterNode) return false; - - std::string strMessage = tx.GetHash().ToString() + boost::lexical_cast(sigTime); - - if(!CMessageSigner::SignMessage(strMessage, vchSig, activeMasternode.keyMasternode)) { - LogPrintf("CDarksendBroadcastTx::Sign -- SignMessage() failed\n"); - return false; - } - - return CheckSignature(activeMasternode.pubKeyMasternode); -} - -bool CDarksendBroadcastTx::CheckSignature(const CPubKey& pubKeyMasternode) -{ - std::string strMessage = tx.GetHash().ToString() + boost::lexical_cast(sigTime); - std::string strError = ""; - - if(!CMessageSigner::VerifyMessage(pubKeyMasternode, vchSig, strMessage, strError)) { - LogPrintf("CDarksendBroadcastTx::CheckSignature -- Got bad dstx signature, error: %s\n", strError); - return false; - } - - return true; -} - -void CDarksendPool::RelayFinalTransaction(const CTransaction& txFinal) -{ - LOCK(cs_vNodes); - BOOST_FOREACH(CNode* pnode, vNodes) - if(pnode->nVersion >= MIN_PRIVATESEND_PEER_PROTO_VERSION) - pnode->PushMessage(NetMsgType::DSFINALTX, nSessionID, txFinal); -} - -void CDarksendPool::RelayIn(const CDarkSendEntry& entry) -{ - if(!pSubmittedToMasternode) return; - - CNode* pnode = FindNode(pSubmittedToMasternode->addr); - if(pnode != NULL) { - LogPrintf("CDarksendPool::RelayIn -- found master, relaying message to %s\n", pnode->addr.ToString()); - pnode->PushMessage(NetMsgType::DSVIN, entry); - } -} - -void CDarksendPool::PushStatus(CNode* pnode, PoolStatusUpdate nStatusUpdate, PoolMessage nMessageID) -{ - if(!pnode) return; - pnode->PushMessage(NetMsgType::DSSTATUSUPDATE, nSessionID, (int)nState, (int)vecEntries.size(), (int)nStatusUpdate, (int)nMessageID); -} - -void CDarksendPool::RelayStatus(PoolStatusUpdate nStatusUpdate, PoolMessage nMessageID) -{ - LOCK(cs_vNodes); - BOOST_FOREACH(CNode* pnode, vNodes) - if(pnode->nVersion >= MIN_PRIVATESEND_PEER_PROTO_VERSION) - PushStatus(pnode, nStatusUpdate, nMessageID); -} - -void CDarksendPool::RelayCompletedTransaction(PoolMessage nMessageID) -{ - LOCK(cs_vNodes); - BOOST_FOREACH(CNode* pnode, vNodes) - if(pnode->nVersion >= MIN_PRIVATESEND_PEER_PROTO_VERSION) - pnode->PushMessage(NetMsgType::DSCOMPLETE, nSessionID, (int)nMessageID); -} - -void CDarksendPool::SetState(PoolState nStateNew) -{ - if(fMasterNode && (nStateNew == POOL_STATE_ERROR || nStateNew == POOL_STATE_SUCCESS)) { - LogPrint("privatesend", "CDarksendPool::SetState -- Can't set state to ERROR or SUCCESS as a Masternode. \n"); - return; - } - - LogPrintf("CDarksendPool::SetState -- nState: %d, nStateNew: %d\n", nState, nStateNew); - nState = nStateNew; -} - -void CDarksendPool::UpdatedBlockTip(const CBlockIndex *pindex) -{ - pCurrentBlockIndex = pindex; - LogPrint("privatesend", "CDarksendPool::UpdatedBlockTip -- pCurrentBlockIndex->nHeight: %d\n", pCurrentBlockIndex->nHeight); - - if(!fLiteMode && masternodeSync.IsMasternodeListSynced()) { - NewBlock(); - } -} - -//TODO: Rename/move to core -void ThreadCheckDarkSendPool() -{ - 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-privatesend"); - - unsigned int nTick = 0; - unsigned int nDoAutoNextRun = nTick + PRIVATESEND_AUTO_TIMEOUT_MIN; - - while (true) - { - MilliSleep(1000); - - // try to sync from all available nodes, one step at a time - masternodeSync.ProcessTick(); - - if(masternodeSync.IsBlockchainSynced() && !ShutdownRequested()) { - - nTick++; - - // make sure to check all masternodes first - mnodeman.Check(); - - // check if we should activate or ping every few minutes, - // slightly postpone first run to give net thread a chance to connect to some peers - if(nTick % MASTERNODE_MIN_MNP_SECONDS == 15) - activeMasternode.ManageState(); - - if(nTick % 60 == 0) { - mnodeman.ProcessMasternodeConnections(); - mnodeman.CheckAndRemove(); - mnpayments.CheckAndRemove(); - instantsend.CheckAndRemove(); - } - if(fMasterNode && (nTick % (60 * 5) == 0)) { - mnodeman.DoFullVerificationStep(); - } - - if(nTick % (60 * 5) == 0) { - governance.DoMaintenance(); - } - - darkSendPool.CheckTimeout(); - darkSendPool.CheckForCompleteQueue(); - - if(nDoAutoNextRun == nTick) { - darkSendPool.DoAutomaticDenominating(); - nDoAutoNextRun = nTick + PRIVATESEND_AUTO_TIMEOUT_MIN + GetRandInt(PRIVATESEND_AUTO_TIMEOUT_MAX - PRIVATESEND_AUTO_TIMEOUT_MIN); - } - } - } -} diff --git a/src/dsnotificationinterface.cpp b/src/dsnotificationinterface.cpp index c2f719a0ed53b..83e7e695f24c7 100644 --- a/src/dsnotificationinterface.cpp +++ b/src/dsnotificationinterface.cpp @@ -3,7 +3,7 @@ // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include "dsnotificationinterface.h" -#include "darksend.h" +#include "privatesend-client.h" #include "instantx.h" #include "governance.h" #include "masternodeman.h" @@ -21,7 +21,7 @@ CDSNotificationInterface::~CDSNotificationInterface() void CDSNotificationInterface::UpdatedBlockTip(const CBlockIndex *pindex) { mnodeman.UpdatedBlockTip(pindex); - darkSendPool.UpdatedBlockTip(pindex); + privateSendClient.UpdatedBlockTip(pindex); instantsend.UpdatedBlockTip(pindex); mnpayments.UpdatedBlockTip(pindex); governance.UpdatedBlockTip(pindex); diff --git a/src/init.cpp b/src/init.cpp index c847ddca722a3..8435caad413a3 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -44,7 +44,6 @@ #endif #include "activemasternode.h" -#include "darksend.h" #include "dsnotificationinterface.h" #include "flat-database.h" #include "governance.h" @@ -58,6 +57,8 @@ #include "masternodeconfig.h" #include "messagesigner.h" #include "netfulfilledman.h" +#include "privatesend-client.h" +#include "privatesend-server.h" #include "spork.h" #include @@ -1839,16 +1840,13 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler) } - nLiquidityProvider = GetArg("-liquidityprovider", nLiquidityProvider); - nLiquidityProvider = std::min(std::max(nLiquidityProvider, 0), 100); - darkSendPool.SetMinBlockSpacing(nLiquidityProvider * 15); + privateSendClient.nLiquidityProvider = std::min(std::max((int)GetArg("-liquidityprovider", DEFAULT_PRIVATESEND_LIQUIDITY), 0), 100); + privateSendClient.SetMinBlockSpacing(privateSendClient.nLiquidityProvider * 15); - fEnablePrivateSend = GetBoolArg("-enableprivatesend", 0); - fPrivateSendMultiSession = GetBoolArg("-privatesendmultisession", DEFAULT_PRIVATESEND_MULTISESSION); - nPrivateSendRounds = GetArg("-privatesendrounds", DEFAULT_PRIVATESEND_ROUNDS); - nPrivateSendRounds = std::min(std::max(nPrivateSendRounds, 2), nLiquidityProvider ? 99999 : 16); - nPrivateSendAmount = GetArg("-privatesendamount", DEFAULT_PRIVATESEND_AMOUNT); - nPrivateSendAmount = std::min(std::max(nPrivateSendAmount, 2), 999999); + privateSendClient.fEnablePrivateSend = GetBoolArg("-enableprivatesend", false); + privateSendClient.fPrivateSendMultiSession = GetBoolArg("-privatesendmultisession", DEFAULT_PRIVATESEND_MULTISESSION); + privateSendClient.nPrivateSendRounds = std::min(std::max((int)GetArg("-privatesendrounds", DEFAULT_PRIVATESEND_ROUNDS), 2), privateSendClient.nLiquidityProvider ? 99999 : 16); + privateSendClient.nPrivateSendAmount = std::min(std::max((int)GetArg("-privatesendamount", DEFAULT_PRIVATESEND_AMOUNT), 2), 999999); fEnableInstantSend = GetBoolArg("-enableinstantsend", 1); nInstantSendDepth = GetArg("-instantsenddepth", DEFAULT_INSTANTSEND_DEPTH); @@ -1862,10 +1860,11 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler) LogPrintf("fLiteMode %d\n", fLiteMode); LogPrintf("nInstantSendDepth %d\n", nInstantSendDepth); - LogPrintf("PrivateSend rounds %d\n", nPrivateSendRounds); - LogPrintf("PrivateSend amount %d\n", nPrivateSendAmount); + LogPrintf("PrivateSend rounds %d\n", privateSendClient.nPrivateSendRounds); + LogPrintf("PrivateSend amount %d\n", privateSendClient.nPrivateSendAmount); - darkSendPool.InitDenominations(); + privateSendClient.InitDenominations(); + privateSendServer.InitDenominations(); // ********************************************************* Step 11b: Load cache data @@ -1913,14 +1912,18 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler) // but don't call it directly to prevent triggering of other listeners like zmq etc. // GetMainSignals().UpdatedBlockTip(chainActive.Tip()); mnodeman.UpdatedBlockTip(chainActive.Tip()); - darkSendPool.UpdatedBlockTip(chainActive.Tip()); + privateSendClient.UpdatedBlockTip(chainActive.Tip()); mnpayments.UpdatedBlockTip(chainActive.Tip()); masternodeSync.UpdatedBlockTip(chainActive.Tip()); governance.UpdatedBlockTip(chainActive.Tip()); - // ********************************************************* Step 11d: start dash-privatesend thread + // ********************************************************* Step 11d: start dash-ps- threads - threadGroup.create_thread(boost::bind(&ThreadCheckDarkSendPool)); + threadGroup.create_thread(boost::bind(&ThreadCheckPrivateSend)); + if (fMasterNode) + threadGroup.create_thread(boost::bind(&ThreadCheckPrivateSendServer)); + else + threadGroup.create_thread(boost::bind(&ThreadCheckPrivateSendClient)); // ********************************************************* Step 12: start node diff --git a/src/main.cpp b/src/main.cpp index dcacaeb5d59bd..fd32558625de8 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -38,12 +38,13 @@ #include "validationinterface.h" #include "versionbits.h" -#include "darksend.h" #include "governance.h" #include "instantx.h" #include "masternode-payments.h" #include "masternode-sync.h" #include "masternodeman.h" +#include "privatesend-client.h" +#include "privatesend-server.h" #include @@ -6319,7 +6320,8 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv, if (found) { //probably one the extensions - darkSendPool.ProcessMessage(pfrom, strCommand, vRecv); + privateSendClient.ProcessMessage(pfrom, strCommand, vRecv); + privateSendServer.ProcessMessage(pfrom, strCommand, vRecv); mnodeman.ProcessMessage(pfrom, strCommand, vRecv); mnpayments.ProcessMessage(pfrom, strCommand, vRecv); instantsend.ProcessMessage(pfrom, strCommand, vRecv); diff --git a/src/masternodeman.cpp b/src/masternodeman.cpp index 3da3ddb68a3c1..5c133b4fb836b 100644 --- a/src/masternodeman.cpp +++ b/src/masternodeman.cpp @@ -4,13 +4,13 @@ #include "activemasternode.h" #include "addrman.h" -#include "darksend.h" #include "governance.h" #include "masternode-payments.h" #include "masternode-sync.h" #include "masternodeman.h" #include "messagesigner.h" #include "netfulfilledman.h" +#include "privatesend-client.h" #include "util.h" /** Masternode manager */ @@ -612,7 +612,7 @@ CMasternode* CMasternodeMan::GetNextMasternodeInQueueForPayment(int nBlockHeight return pBestMasternode; } -CMasternode* CMasternodeMan::FindRandomNotInVec(const std::vector &vecToExclude, int nProtocolVersion) +masternode_info_t CMasternodeMan::FindRandomNotInVec(const std::vector &vecToExclude, int nProtocolVersion) { LOCK(cs); @@ -622,7 +622,7 @@ CMasternode* CMasternodeMan::FindRandomNotInVec(const std::vector &vecToE int nCountNotExcluded = nCountEnabled - vecToExclude.size(); LogPrintf("CMasternodeMan::FindRandomNotInVec -- %d enabled masternodes, %d masternodes to choose from\n", nCountEnabled, nCountNotExcluded); - if(nCountNotExcluded < 1) return NULL; + if(nCountNotExcluded < 1) return masternode_info_t(); // fill a vector of pointers std::vector vpMasternodesShuffled; @@ -648,11 +648,11 @@ CMasternode* CMasternodeMan::FindRandomNotInVec(const std::vector &vecToE if(fExclude) continue; // found the one not in vecToExclude LogPrint("masternode", "CMasternodeMan::FindRandomNotInVec -- found, masternode=%s\n", pmn->vin.prevout.ToStringShort()); - return pmn; + return pmn->GetInfo(); } LogPrint("masternode", "CMasternodeMan::FindRandomNotInVec -- failed\n"); - return NULL; + return masternode_info_t(); } int CMasternodeMan::GetMasternodeRank(const CTxIn& vin, int nBlockHeight, int nMinProtocol, bool fOnlyActive) @@ -766,7 +766,7 @@ void CMasternodeMan::ProcessMasternodeConnections() LOCK(cs_vNodes); BOOST_FOREACH(CNode* pnode, vNodes) { if(pnode->fMasternode) { - if(darkSendPool.pSubmittedToMasternode != NULL && pnode->addr == darkSendPool.pSubmittedToMasternode->addr) continue; + if(privateSendClient.infoMixingMasternode.fInfoValid && pnode->addr == privateSendClient.infoMixingMasternode.addr) continue; LogPrintf("Closing Masternode connection: peer=%d, addr=%s\n", pnode->id, pnode->addr.ToString()); pnode->fDisconnect = true; } @@ -1512,6 +1512,18 @@ void CMasternodeMan::UpdateLastPaid() IsFirstRun = !masternodeSync.IsWinnersListSynced(); } +bool CMasternodeMan::UpdateLastDsq(const CTxIn& vin) +{ + masternode_info_t info; + LOCK(cs); + CMasternode* pMN = Find(vin); + if(!pMN) + return false; + pMN->nLastDsq = nDsqCount; + pMN->fAllowMixingTx = true; + return true; +} + void CMasternodeMan::CheckAndRebuildMasternodeIndex() { LOCK(cs); diff --git a/src/masternodeman.h b/src/masternodeman.h index 05f2d998fe10c..9e1f7d0934a98 100644 --- a/src/masternodeman.h +++ b/src/masternodeman.h @@ -290,7 +290,7 @@ class CMasternodeMan CMasternode* GetNextMasternodeInQueueForPayment(bool fFilterSigTime, int& nCount); /// Find a random entry - CMasternode* FindRandomNotInVec(const std::vector &vecToExclude, int nProtocolVersion = -1); + masternode_info_t FindRandomNotInVec(const std::vector &vecToExclude, int nProtocolVersion = -1); std::vector GetFullMasternodeVector() { return vMasternodes; } @@ -322,6 +322,7 @@ class CMasternodeMan bool IsMnbRecoveryRequested(const uint256& hash) { return mMnbRecoveryRequests.count(hash); } void UpdateLastPaid(); + bool UpdateLastDsq(const CTxIn& vin); void CheckAndRebuildMasternodeIndex(); diff --git a/src/net.cpp b/src/net.cpp index 327eff08d52b7..d676a1d448697 100644 --- a/src/net.cpp +++ b/src/net.cpp @@ -22,10 +22,10 @@ #include "wallet/wallet.h" #include "utilstrencodings.h" -#include "darksend.h" #include "instantx.h" #include "masternode-sync.h" #include "masternodeman.h" +#include "privatesend.h" #ifdef WIN32 #include diff --git a/src/privatesend-client.cpp b/src/privatesend-client.cpp new file mode 100644 index 0000000000000..6b47ce9c9eae3 --- /dev/null +++ b/src/privatesend-client.cpp @@ -0,0 +1,1395 @@ +// 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" + +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 scriptChange; + CPubKey vchPubKey; + // use a unique change address + assert(reservekey.GetReservedKey(vchPubKey)); // should never fail, as we just unlocked + scriptChange = GetScriptForDestination(vchPubKey.GetID()); + reservekey.KeepKey(); + + // add new output + CTxOut txout(nValueDenom, scriptChange); + 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)); // 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() +{ + 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)); // 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 + CReserveKey reservekeyDenom(pwalletMain); + + // 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 10 times until it can't be added again + while(nValueLeft - nDenomValue >= 0 && nOutputs <= 10) { + CScript scriptDenom; + CPubKey vchPubKey; + //use a unique change address + assert(reservekeyDenom.GetReservedKey(vchPubKey)); // should never fail, as we just unlocked + scriptDenom = GetScriptForDestination(vchPubKey.GetID()); + // TODO: do not keep reservekeyDenom here + reservekeyDenom.KeepKey(); + + vecSend.push_back((CRecipient){ scriptDenom, nDenomValue, false }); + + //increment outputs and subtract denomination amount + nOutputs++; + nValueLeft -= nDenomValue; + LogPrintf("CreateDenominated1: nOutputsTotal: %d, nOutputs: %d, nValueLeft: %f\n", 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); + // TODO: return reservekeyDenom here + reservekeyCollateral.ReturnKey(); + return false; + } + + // TODO: keep reservekeyDenom here + reservekeyCollateral.KeepKey(); + + 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); + } + } + } +} diff --git a/src/privatesend-client.h b/src/privatesend-client.h new file mode 100644 index 0000000000000..f9ff0adfb9535 --- /dev/null +++ b/src/privatesend-client.h @@ -0,0 +1,143 @@ +// 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. + +#ifndef PRIVATESENDCLIENT_H +#define PRIVATESENDCLIENT_H + +#include "masternode.h" +#include "privatesend.h" +#include "wallet/wallet.h" + +class CPrivateSendClient; + +static const int DENOMS_COUNT_MAX = 100; + +static const int DEFAULT_PRIVATESEND_ROUNDS = 2; +static const int DEFAULT_PRIVATESEND_AMOUNT = 1000; +static const int DEFAULT_PRIVATESEND_LIQUIDITY = 0; +static const bool DEFAULT_PRIVATESEND_MULTISESSION = false; + +// Warn user if mixing in gui or try to create backup if mixing in daemon mode +// when we have only this many keys left +static const int PRIVATESEND_KEYS_THRESHOLD_WARNING = 100; +// Stop mixing completely, it's too dangerous to continue when we have only this many keys left +static const int PRIVATESEND_KEYS_THRESHOLD_STOP = 50; + +// The main object for accessing mixing +extern CPrivateSendClient privateSendClient; + +/** Used to keep track of current status of mixing pool + */ +class CPrivateSendClient : public CPrivateSend +{ +private: + mutable CCriticalSection cs_darksend; + + // Keep track of the used Masternodes + std::vector vecMasternodesUsed; + + std::vector vecDenominationsSkipped; + std::vector vecOutPointLocked; + + int nCachedLastSuccessBlock; + int nMinBlockSpacing; //required blocks between mixes + const CBlockIndex *pCurrentBlockIndex; // Keep track of current block index + + int nEntriesCount; + bool fLastEntryAccepted; + + std::string strLastMessage; + std::string strAutoDenomResult; + + CMutableTransaction txMyCollateral; // client side collateral + + /// Check for process + void CheckPool(); + void CompletedTransaction(PoolMessage nMessageID); + + bool IsDenomSkipped(CAmount nDenomValue) { + return std::find(vecDenominationsSkipped.begin(), vecDenominationsSkipped.end(), nDenomValue) != vecDenominationsSkipped.end(); + } + + // Make sure we have enough keys since last backup + bool CheckAutomaticBackup(); + bool JoinExistingQueue(CAmount nBalanceNeedsAnonymized); + bool StartNewQueue(CAmount nValueMin, CAmount nBalanceNeedsAnonymized); + + /// Create denominations + bool CreateDenominated(); + bool CreateDenominated(const CompactTallyItem& tallyItem, bool fCreateMixingCollaterals); + + /// Split up large inputs or make fee sized inputs + bool MakeCollateralAmounts(); + bool MakeCollateralAmounts(const CompactTallyItem& tallyItem); + + /// As a client, submit part of a future mixing transaction to a Masternode to start the process + bool SubmitDenominate(); + /// step 1: prepare denominated inputs and outputs + bool PrepareDenominate(int nMinRounds, int nMaxRounds, std::string& strErrorRet, std::vector& vecTxInRet, std::vector& vecTxOutRet); + /// step 2: send denominated inputs and outputs prepared in step 1 + bool SendDenominate(const std::vector& vecTxIn, const std::vector& vecTxOut); + + /// Get Masternode updates about the progress of mixing + bool CheckPoolStateUpdate(PoolState nStateNew, int nEntriesCountNew, PoolStatusUpdate nStatusUpdate, PoolMessage nMessageID, int nSessionIDNew=0); + // Set the 'state' value, with some logging and capturing when the state changed + void SetState(PoolState nStateNew); + + /// As a client, check and sign the final transaction + bool SignFinalTransaction(const CTransaction& finalTransactionNew, CNode* pnode); + + void RelayIn(const CDarkSendEntry& entry); + + void SetNull(); + +public: + int nPrivateSendRounds; + int nPrivateSendAmount; + int nLiquidityProvider; + bool fEnablePrivateSend; + bool fPrivateSendMultiSession; + + masternode_info_t infoMixingMasternode; + int nCachedNumBlocks; //used for the overview screen + bool fCreateAutoBackups; //builtin support for automatic backups + + CPrivateSendClient() : + nCachedLastSuccessBlock(0), + nMinBlockSpacing(0), + txMyCollateral(CMutableTransaction()), + nPrivateSendRounds(DEFAULT_PRIVATESEND_ROUNDS), + nPrivateSendAmount(DEFAULT_PRIVATESEND_AMOUNT), + nLiquidityProvider(DEFAULT_PRIVATESEND_LIQUIDITY), + fEnablePrivateSend(false), + fPrivateSendMultiSession(DEFAULT_PRIVATESEND_MULTISESSION), + nCachedNumBlocks(std::numeric_limits::max()), + fCreateAutoBackups(true) { SetNull(); } + + void ProcessMessage(CNode* pfrom, std::string& strCommand, CDataStream& vRecv); + + void ClearSkippedDenominations() { vecDenominationsSkipped.clear(); } + + void SetMinBlockSpacing(int nMinBlockSpacingIn) { nMinBlockSpacing = nMinBlockSpacingIn; } + + void ResetPool(); + + void UnlockCoins(); + + std::string GetStatus(); + + /// Passively run mixing in the background according to the configuration in settings + bool DoAutomaticDenominating(bool fDryRun=false); + + void CheckTimeout(); + + /// Process a new block + void NewBlock(); + + void UpdatedBlockTip(const CBlockIndex *pindex); +}; + +void ThreadCheckPrivateSendClient(); + +#endif diff --git a/src/privatesend-server.cpp b/src/privatesend-server.cpp new file mode 100644 index 0000000000000..5777d10f69c2a --- /dev/null +++ b/src/privatesend-server.cpp @@ -0,0 +1,861 @@ +// 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-server.h" + +#include "activemasternode.h" +#include "consensus/validation.h" +#include "core_io.h" +#include "init.h" +#include "masternode-sync.h" +#include "masternodeman.h" +#include "txmempool.h" +#include "util.h" +#include "utilmoneystr.h" + +CPrivateSendServer privateSendServer; + +void CPrivateSendServer::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::DSACCEPT) { + + if(pfrom->nVersion < MIN_PRIVATESEND_PEER_PROTO_VERSION) { + LogPrintf("DSACCEPT -- incompatible version! nVersion: %d\n", pfrom->nVersion); + PushStatus(pfrom, STATUS_REJECTED, ERR_VERSION); + return; + } + + if(IsSessionReady()) { + // too many users in this session already, reject new ones + LogPrintf("DSACCEPT -- queue is already full!\n"); + PushStatus(pfrom, STATUS_ACCEPTED, ERR_QUEUE_FULL); + return; + } + + int nDenom; + CTransaction txCollateral; + vRecv >> nDenom >> txCollateral; + + LogPrint("privatesend", "DSACCEPT -- nDenom %d (%s) txCollateral %s", nDenom, GetDenominationsToString(nDenom), txCollateral.ToString()); + + CMasternode* pmn = mnodeman.Find(activeMasternode.vin); + if(pmn == NULL) { + PushStatus(pfrom, STATUS_REJECTED, ERR_MN_LIST); + return; + } + + if(vecSessionCollaterals.size() == 0 && pmn->nLastDsq != 0 && + pmn->nLastDsq + mnodeman.CountEnabled(MIN_PRIVATESEND_PEER_PROTO_VERSION)/5 > mnodeman.nDsqCount) + { + LogPrintf("DSACCEPT -- last dsq too recent, must wait: addr=%s\n", pfrom->addr.ToString()); + PushStatus(pfrom, STATUS_REJECTED, ERR_RECENT); + return; + } + + PoolMessage nMessageID = MSG_NOERR; + + bool fResult = nSessionID == 0 ? CreateNewSession(nDenom, txCollateral, nMessageID) + : AddUserToExistingSession(nDenom, txCollateral, nMessageID); + if(fResult) { + LogPrintf("DSACCEPT -- is compatible, please submit!\n"); + PushStatus(pfrom, STATUS_ACCEPTED, nMessageID); + return; + } else { + LogPrintf("DSACCEPT -- not compatible with existing transactions!\n"); + PushStatus(pfrom, STATUS_REJECTED, nMessageID); + return; + } + + } else 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; + + CMasternode* pmn = mnodeman.Find(dsq.vin); + if(pmn == NULL) return; + + if(!dsq.CheckSignature(pmn->pubKeyMasternode)) { + // we probably have outdated info + mnodeman.AskForMN(pfrom, dsq.vin); + return; + } + + if(!dsq.fReady) { + 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", pmn->addr.ToString()); + return; + } + } + + int nThreshold = pmn->nLastDsq + mnodeman.CountEnabled(MIN_PRIVATESEND_PEER_PROTO_VERSION)/5; + LogPrint("privatesend", "DSQUEUE -- nLastDsq: %d threshold: %d nDsqCount: %d\n", pmn->nLastDsq, nThreshold, mnodeman.nDsqCount); + //don't allow a few nodes to dominate the queuing process + if(pmn->nLastDsq != 0 && nThreshold > mnodeman.nDsqCount) { + LogPrint("privatesend", "DSQUEUE -- Masternode %s is sending too many dsq messages\n", pmn->addr.ToString()); + return; + } + mnodeman.nDsqCount++; + pmn->nLastDsq = mnodeman.nDsqCount; + pmn->fAllowMixingTx = true; + + LogPrint("privatesend", "DSQUEUE -- new PrivateSend queue (%s) from masternode %s\n", dsq.ToString(), pmn->addr.ToString()); + vecDarksendQueue.push_back(dsq); + dsq.Relay(); + } + + } else if(strCommand == NetMsgType::DSVIN) { + + if(pfrom->nVersion < MIN_PRIVATESEND_PEER_PROTO_VERSION) { + LogPrintf("DSVIN -- incompatible version! nVersion: %d\n", pfrom->nVersion); + PushStatus(pfrom, STATUS_REJECTED, ERR_VERSION); + return; + } + + //do we have enough users in the current session? + if(!IsSessionReady()) { + LogPrintf("DSVIN -- session not complete!\n"); + PushStatus(pfrom, STATUS_REJECTED, ERR_SESSION); + return; + } + + CDarkSendEntry entry; + vRecv >> entry; + + LogPrint("privatesend", "DSVIN -- txCollateral %s", entry.txCollateral.ToString()); + + if(entry.vecTxDSIn.size() > PRIVATESEND_ENTRY_MAX_SIZE) { + LogPrintf("DSVIN -- ERROR: too many inputs! %d/%d\n", entry.vecTxDSIn.size(), PRIVATESEND_ENTRY_MAX_SIZE); + PushStatus(pfrom, STATUS_REJECTED, ERR_MAXIMUM); + return; + } + + if(entry.vecTxDSOut.size() > PRIVATESEND_ENTRY_MAX_SIZE) { + LogPrintf("DSVIN -- ERROR: too many outputs! %d/%d\n", entry.vecTxDSOut.size(), PRIVATESEND_ENTRY_MAX_SIZE); + PushStatus(pfrom, STATUS_REJECTED, ERR_MAXIMUM); + return; + } + + //do we have the same denominations as the current session? + if(!IsOutputsCompatibleWithSessionDenom(entry.vecTxDSOut)) { + LogPrintf("DSVIN -- not compatible with existing transactions!\n"); + PushStatus(pfrom, STATUS_REJECTED, ERR_EXISTING_TX); + return; + } + + //check it like a transaction + { + CAmount nValueIn = 0; + CAmount nValueOut = 0; + + CMutableTransaction tx; + + BOOST_FOREACH(const CTxOut txout, entry.vecTxDSOut) { + nValueOut += txout.nValue; + tx.vout.push_back(txout); + + if(txout.scriptPubKey.size() != 25) { + LogPrintf("DSVIN -- non-standard pubkey detected! scriptPubKey=%s\n", ScriptToAsmStr(txout.scriptPubKey)); + PushStatus(pfrom, STATUS_REJECTED, ERR_NON_STANDARD_PUBKEY); + return; + } + if(!txout.scriptPubKey.IsNormalPaymentScript()) { + LogPrintf("DSVIN -- invalid script! scriptPubKey=%s\n", ScriptToAsmStr(txout.scriptPubKey)); + PushStatus(pfrom, STATUS_REJECTED, ERR_INVALID_SCRIPT); + return; + } + } + + BOOST_FOREACH(const CTxIn txin, entry.vecTxDSIn) { + tx.vin.push_back(txin); + + LogPrint("privatesend", "DSVIN -- txin=%s\n", txin.ToString()); + + CTransaction txPrev; + uint256 hash; + if(GetTransaction(txin.prevout.hash, txPrev, Params().GetConsensus(), hash, true)) { + if(txPrev.vout.size() > txin.prevout.n) + nValueIn += txPrev.vout[txin.prevout.n].nValue; + } else { + LogPrintf("DSVIN -- missing input! tx=%s", tx.ToString()); + PushStatus(pfrom, STATUS_REJECTED, ERR_MISSING_TX); + return; + } + } + + // There should be no fee in mixing tx + CAmount nFee = nValueIn - nValueOut; + if(nFee != 0) { + LogPrintf("DSVIN -- there should be no fee in mixing tx! fees: %lld, tx=%s", nFee, tx.ToString()); + PushStatus(pfrom, STATUS_REJECTED, ERR_FEES); + return; + } + + { + LOCK(cs_main); + CValidationState validationState; + mempool.PrioritiseTransaction(tx.GetHash(), tx.GetHash().ToString(), 1000, 0.1*COIN); + if(!AcceptToMemoryPool(mempool, validationState, CTransaction(tx), false, NULL, false, true, true)) { + LogPrintf("DSVIN -- transaction not valid! tx=%s", tx.ToString()); + PushStatus(pfrom, STATUS_REJECTED, ERR_INVALID_TX); + return; + } + } + } + + PoolMessage nMessageID = MSG_NOERR; + + if(AddEntry(entry, nMessageID)) { + PushStatus(pfrom, STATUS_ACCEPTED, nMessageID); + CheckPool(); + RelayStatus(STATUS_ACCEPTED); + } else { + PushStatus(pfrom, STATUS_REJECTED, nMessageID); + SetNull(); + } + + } else if(strCommand == NetMsgType::DSSIGNFINALTX) { + + if(pfrom->nVersion < MIN_PRIVATESEND_PEER_PROTO_VERSION) { + LogPrintf("DSSIGNFINALTX -- incompatible version! nVersion: %d\n", pfrom->nVersion); + return; + } + + std::vector vecTxIn; + vRecv >> vecTxIn; + + LogPrint("privatesend", "DSSIGNFINALTX -- vecTxIn.size() %s\n", vecTxIn.size()); + + int nTxInIndex = 0; + int nTxInsCount = (int)vecTxIn.size(); + + BOOST_FOREACH(const CTxIn txin, vecTxIn) { + nTxInIndex++; + if(!AddScriptSig(txin)) { + LogPrint("privatesend", "DSSIGNFINALTX -- AddScriptSig() failed at %d/%d, session: %d\n", nTxInIndex, nTxInsCount, nSessionID); + RelayStatus(STATUS_REJECTED); + return; + } + LogPrint("privatesend", "DSSIGNFINALTX -- AddScriptSig() %d/%d success\n", nTxInIndex, nTxInsCount); + } + // all is good + CheckPool(); + } +} + +void CPrivateSendServer::SetNull() +{ + // MN side + vecSessionCollaterals.clear(); + + CPrivateSend::SetNull(); +} + +// +// Check the mixing progress and send client updates if a Masternode +// +void CPrivateSendServer::CheckPool() +{ + if(fMasterNode) { + LogPrint("privatesend", "CPrivateSendServer::CheckPool -- entries count %lu\n", GetEntriesCount()); + + // If entries are full, create finalized transaction + if(nState == POOL_STATE_ACCEPTING_ENTRIES && GetEntriesCount() >= GetMaxPoolTransactions()) { + LogPrint("privatesend", "CPrivateSendServer::CheckPool -- FINALIZE TRANSACTIONS\n"); + CreateFinalTransaction(); + return; + } + + // If we have all of the signatures, try to compile the transaction + if(nState == POOL_STATE_SIGNING && IsSignaturesComplete()) { + LogPrint("privatesend", "CPrivateSendServer::CheckPool -- SIGNING\n"); + CommitFinalTransaction(); + return; + } + } + + // reset if we're here for 10 seconds + if((nState == POOL_STATE_ERROR || nState == POOL_STATE_SUCCESS) && GetTimeMillis() - nTimeLastSuccessfulStep >= 10000) { + LogPrint("privatesend", "CPrivateSendServer::CheckPool -- timeout, RESETTING\n"); + SetNull(); + } +} + +void CPrivateSendServer::CreateFinalTransaction() +{ + LogPrint("privatesend", "CPrivateSendServer::CreateFinalTransaction -- FINALIZE TRANSACTIONS\n"); + + CMutableTransaction txNew; + + // make our new transaction + for(int i = 0; i < GetEntriesCount(); i++) { + BOOST_FOREACH(const CTxDSOut& txdsout, vecEntries[i].vecTxDSOut) + txNew.vout.push_back(txdsout); + + BOOST_FOREACH(const CTxDSIn& txdsin, vecEntries[i].vecTxDSIn) + txNew.vin.push_back(txdsin); + } + + // BIP69 https://github.com/kristovatlas/bips/blob/master/bip-0069.mediawiki + sort(txNew.vin.begin(), txNew.vin.end()); + sort(txNew.vout.begin(), txNew.vout.end()); + + finalMutableTransaction = txNew; + LogPrint("privatesend", "CPrivateSendServer::CreateFinalTransaction -- finalMutableTransaction=%s", txNew.ToString()); + + // request signatures from clients + RelayFinalTransaction(finalMutableTransaction); + SetState(POOL_STATE_SIGNING); +} + +void CPrivateSendServer::CommitFinalTransaction() +{ + if(!fMasterNode) return; // check and relay final tx only on masternode + + CTransaction finalTransaction = CTransaction(finalMutableTransaction); + uint256 hashTx = finalTransaction.GetHash(); + + LogPrint("privatesend", "CPrivateSendServer::CommitFinalTransaction -- finalTransaction=%s", finalTransaction.ToString()); + + { + // See if the transaction is valid + TRY_LOCK(cs_main, lockMain); + CValidationState validationState; + mempool.PrioritiseTransaction(hashTx, hashTx.ToString(), 1000, 0.1*COIN); + if(!lockMain || !AcceptToMemoryPool(mempool, validationState, finalTransaction, false, NULL, false, true, true)) + { + LogPrintf("CPrivateSendServer::CommitFinalTransaction -- AcceptToMemoryPool() error: Transaction not valid\n"); + SetNull(); + // not much we can do in this case, just notify clients + RelayCompletedTransaction(ERR_INVALID_TX); + return; + } + } + + LogPrintf("CPrivateSendServer::CommitFinalTransaction -- CREATING DSTX\n"); + + // create and sign masternode dstx transaction + if(!mapDarksendBroadcastTxes.count(hashTx)) { + CDarksendBroadcastTx dstx(finalTransaction, activeMasternode.vin, GetAdjustedTime()); + dstx.Sign(); + mapDarksendBroadcastTxes.insert(std::make_pair(hashTx, dstx)); + } + + LogPrintf("CPrivateSendServer::CommitFinalTransaction -- TRANSMITTING DSTX\n"); + + CInv inv(MSG_DSTX, hashTx); + RelayInv(inv); + + // Tell the clients it was successful + RelayCompletedTransaction(MSG_SUCCESS); + + // Randomly charge clients + ChargeRandomFees(); + + // Reset + LogPrint("privatesend", "CPrivateSendServer::CommitFinalTransaction -- COMPLETED -- RESETTING\n"); + SetNull(); +} + +// +// Charge clients a fee if they're abusive +// +// Why bother? PrivateSend uses collateral to ensure abuse to the process is kept to a minimum. +// The submission and signing stages are completely separate. In the cases where +// a client submits a transaction then refused to sign, there must be a cost. Otherwise they +// would be able to do this over and over again and bring the mixing to a hault. +// +// How does this work? Messages to Masternodes come in via NetMsgType::DSVIN, these require a valid collateral +// transaction for the client to be able to enter the pool. This transaction is kept by the Masternode +// until the transaction is either complete or fails. +// +void CPrivateSendServer::ChargeFees() +{ + if(!fMasterNode) return; + + //we don't need to charge collateral for every offence. + if(GetRandInt(100) > 33) return; + + std::vector vecOffendersCollaterals; + + if(nState == POOL_STATE_ACCEPTING_ENTRIES) { + BOOST_FOREACH(const CTransaction& txCollateral, vecSessionCollaterals) { + bool fFound = false; + BOOST_FOREACH(const CDarkSendEntry& entry, vecEntries) + if(entry.txCollateral == txCollateral) + fFound = true; + + // This queue entry didn't send us the promised transaction + if(!fFound) { + LogPrintf("CPrivateSendServer::ChargeFees -- found uncooperative node (didn't send transaction), found offence\n"); + vecOffendersCollaterals.push_back(txCollateral); + } + } + } + + if(nState == POOL_STATE_SIGNING) { + // who didn't sign? + BOOST_FOREACH(const CDarkSendEntry entry, vecEntries) { + BOOST_FOREACH(const CTxDSIn txdsin, entry.vecTxDSIn) { + if(!txdsin.fHasSig) { + LogPrintf("CPrivateSendServer::ChargeFees -- found uncooperative node (didn't sign), found offence\n"); + vecOffendersCollaterals.push_back(entry.txCollateral); + } + } + } + } + + // no offences found + if(vecOffendersCollaterals.empty()) return; + + //mostly offending? Charge sometimes + if((int)vecOffendersCollaterals.size() >= Params().PoolMaxTransactions() - 1 && GetRandInt(100) > 33) return; + + //everyone is an offender? That's not right + if((int)vecOffendersCollaterals.size() >= Params().PoolMaxTransactions()) return; + + //charge one of the offenders randomly + std::random_shuffle(vecOffendersCollaterals.begin(), vecOffendersCollaterals.end()); + + if(nState == POOL_STATE_ACCEPTING_ENTRIES || nState == POOL_STATE_SIGNING) { + LogPrintf("CPrivateSendServer::ChargeFees -- found uncooperative node (didn't %s transaction), charging fees: %s\n", + (nState == POOL_STATE_SIGNING) ? "sign" : "send", vecOffendersCollaterals[0].ToString()); + + LOCK(cs_main); + + CValidationState state; + bool fMissingInputs; + if(!AcceptToMemoryPool(mempool, state, vecOffendersCollaterals[0], false, &fMissingInputs, false, true)) { + // should never really happen + LogPrintf("CPrivateSendServer::ChargeFees -- ERROR: AcceptToMemoryPool failed!\n"); + } else { + RelayTransaction(vecOffendersCollaterals[0]); + } + } +} + +/* + Charge the collateral randomly. + Mixing is completely free, to pay miners we randomly pay the collateral of users. + + Collateral Fee Charges: + + Being that mixing has "no fees" we need to have some kind of cost associated + with using it to stop abuse. Otherwise it could serve as an attack vector and + allow endless transaction that would bloat Dash and make it unusable. To + stop these kinds of attacks 1 in 10 successful transactions are charged. This + adds up to a cost of 0.001DRK per transaction on average. +*/ +void CPrivateSendServer::ChargeRandomFees() +{ + if(!fMasterNode) return; + + LOCK(cs_main); + + BOOST_FOREACH(const CTransaction& txCollateral, vecSessionCollaterals) { + + if(GetRandInt(100) > 10) return; + + LogPrintf("CPrivateSendServer::ChargeRandomFees -- charging random fees, txCollateral=%s", txCollateral.ToString()); + + CValidationState state; + bool fMissingInputs; + if(!AcceptToMemoryPool(mempool, state, txCollateral, false, &fMissingInputs, false, true)) { + // should never really happen + LogPrintf("CPrivateSendServer::ChargeRandomFees -- ERROR: AcceptToMemoryPool failed!\n"); + } else { + RelayTransaction(txCollateral); + } + } +} + +// +// Check for various timeouts (queue objects, mixing, etc) +// +void CPrivateSendServer::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", "CPrivateSendServer::CheckTimeout -- Removing expired queue (%s)\n", (*it).ToString()); + it = vecDarksendQueue.erase(it); + } else ++it; + } + } + + if(!fMasterNode) return; + + 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", "CPrivateSendServer::CheckTimeout -- %s timed out (%ds) -- restting\n", + (nState == POOL_STATE_SIGNING) ? "Signing" : "Session", nTimeout); + ChargeFees(); + SetNull(); + SetState(POOL_STATE_ERROR); + } +} + +/* + Check to see if we're ready for submissions from clients + After receiving multiple dsa messages, the queue will switch to "accepting entries" + which is the active state right before merging the transaction +*/ +void CPrivateSendServer::CheckForCompleteQueue() +{ + if(!fMasterNode) return; + + if(nState == POOL_STATE_QUEUE && IsSessionReady()) { + SetState(POOL_STATE_ACCEPTING_ENTRIES); + + CDarksendQueue dsq(nSessionDenom, activeMasternode.vin, GetTime(), true); + LogPrint("privatesend", "CPrivateSendServer::CheckForCompleteQueue -- queue is ready, signing and relaying (%s)\n", dsq.ToString()); + dsq.Sign(); + dsq.Relay(); + } +} + +// Check to make sure a given input matches an input in the pool and its scriptSig is valid +bool CPrivateSendServer::IsInputScriptSigValid(const CTxIn& txin) +{ + CMutableTransaction txNew; + txNew.vin.clear(); + txNew.vout.clear(); + + int i = 0; + int nTxInIndex = -1; + CScript sigPubKey = CScript(); + + BOOST_FOREACH(CDarkSendEntry& entry, vecEntries) { + + BOOST_FOREACH(const CTxDSOut& txdsout, entry.vecTxDSOut) + txNew.vout.push_back(txdsout); + + BOOST_FOREACH(const CTxDSIn& txdsin, entry.vecTxDSIn) { + txNew.vin.push_back(txdsin); + + if(txdsin.prevout == txin.prevout) { + nTxInIndex = i; + sigPubKey = txdsin.prevPubKey; + } + i++; + } + } + + if(nTxInIndex >= 0) { //might have to do this one input at a time? + txNew.vin[nTxInIndex].scriptSig = txin.scriptSig; + LogPrint("privatesend", "CPrivateSendServer::IsInputScriptSigValid -- verifying scriptSig %s\n", ScriptToAsmStr(txin.scriptSig).substr(0,24)); + if(!VerifyScript(txNew.vin[nTxInIndex].scriptSig, sigPubKey, SCRIPT_VERIFY_P2SH | SCRIPT_VERIFY_STRICTENC, MutableTransactionSignatureChecker(&txNew, nTxInIndex))) { + LogPrint("privatesend", "CPrivateSendServer::IsInputScriptSigValid -- VerifyScript() failed on input %d\n", nTxInIndex); + return false; + } + } else { + LogPrint("privatesend", "CPrivateSendServer::IsInputScriptSigValid -- Failed to find matching input in pool, %s\n", txin.ToString()); + return false; + } + + LogPrint("privatesend", "CPrivateSendServer::IsInputScriptSigValid -- Successfully validated input and scriptSig\n"); + return true; +} + +// +// Add a clients transaction to the pool +// +bool CPrivateSendServer::AddEntry(const CDarkSendEntry& entryNew, PoolMessage& nMessageIDRet) +{ + if(!fMasterNode) return false; + + BOOST_FOREACH(CTxIn txin, entryNew.vecTxDSIn) { + if(txin.prevout.IsNull()) { + LogPrint("privatesend", "CPrivateSendServer::AddEntry -- input not valid!\n"); + nMessageIDRet = ERR_INVALID_INPUT; + return false; + } + } + + if(!IsCollateralValid(entryNew.txCollateral)) { + LogPrint("privatesend", "CPrivateSendServer::AddEntry -- collateral not valid!\n"); + nMessageIDRet = ERR_INVALID_COLLATERAL; + return false; + } + + if(GetEntriesCount() >= GetMaxPoolTransactions()) { + LogPrint("privatesend", "CPrivateSendServer::AddEntry -- entries is full!\n"); + nMessageIDRet = ERR_ENTRIES_FULL; + return false; + } + + BOOST_FOREACH(CTxIn txin, entryNew.vecTxDSIn) { + LogPrint("privatesend", "looking for txin -- %s\n", txin.ToString()); + BOOST_FOREACH(const CDarkSendEntry& entry, vecEntries) { + BOOST_FOREACH(const CTxDSIn& txdsin, entry.vecTxDSIn) { + if(txdsin.prevout == txin.prevout) { + LogPrint("privatesend", "CPrivateSendServer::AddEntry -- found in txin\n"); + nMessageIDRet = ERR_ALREADY_HAVE; + return false; + } + } + } + } + + vecEntries.push_back(entryNew); + + LogPrint("privatesend", "CPrivateSendServer::AddEntry -- adding entry\n"); + nMessageIDRet = MSG_ENTRIES_ADDED; + nTimeLastSuccessfulStep = GetTimeMillis(); + + return true; +} + +bool CPrivateSendServer::AddScriptSig(const CTxIn& txinNew) +{ + LogPrint("privatesend", "CPrivateSendServer::AddScriptSig -- scriptSig=%s\n", ScriptToAsmStr(txinNew.scriptSig).substr(0,24)); + + BOOST_FOREACH(const CDarkSendEntry& entry, vecEntries) { + BOOST_FOREACH(const CTxDSIn& txdsin, entry.vecTxDSIn) { + if(txdsin.scriptSig == txinNew.scriptSig) { + LogPrint("privatesend", "CPrivateSendServer::AddScriptSig -- already exists\n"); + return false; + } + } + } + + if(!IsInputScriptSigValid(txinNew)) { + LogPrint("privatesend", "CPrivateSendServer::AddScriptSig -- Invalid scriptSig\n"); + return false; + } + + LogPrint("privatesend", "CPrivateSendServer::AddScriptSig -- scriptSig=%s new\n", ScriptToAsmStr(txinNew.scriptSig).substr(0,24)); + + BOOST_FOREACH(CTxIn& txin, finalMutableTransaction.vin) { + if(txinNew.prevout == txin.prevout && txin.nSequence == txinNew.nSequence) { + txin.scriptSig = txinNew.scriptSig; + txin.prevPubKey = txinNew.prevPubKey; + LogPrint("privatesend", "CPrivateSendServer::AddScriptSig -- adding to finalMutableTransaction, scriptSig=%s\n", ScriptToAsmStr(txinNew.scriptSig).substr(0,24)); + } + } + for(int i = 0; i < GetEntriesCount(); i++) { + if(vecEntries[i].AddScriptSig(txinNew)) { + LogPrint("privatesend", "CPrivateSendServer::AddScriptSig -- adding to entries, scriptSig=%s\n", ScriptToAsmStr(txinNew.scriptSig).substr(0,24)); + return true; + } + } + + LogPrintf("CPrivateSendServer::AddScriptSig -- Couldn't set sig!\n" ); + return false; +} + +// Check to make sure everything is signed +bool CPrivateSendServer::IsSignaturesComplete() +{ + BOOST_FOREACH(const CDarkSendEntry& entry, vecEntries) + BOOST_FOREACH(const CTxDSIn& txdsin, entry.vecTxDSIn) + if(!txdsin.fHasSig) return false; + + return true; +} + +bool CPrivateSendServer::IsOutputsCompatibleWithSessionDenom(const std::vector& vecTxDSOut) +{ + if(GetDenominations(vecTxDSOut) == 0) return false; + + BOOST_FOREACH(const CDarkSendEntry entry, vecEntries) { + LogPrintf("CPrivateSendServer::IsOutputsCompatibleWithSessionDenom -- vecTxDSOut denom %d, entry.vecTxDSOut denom %d\n", GetDenominations(vecTxDSOut), GetDenominations(entry.vecTxDSOut)); + if(GetDenominations(vecTxDSOut) != GetDenominations(entry.vecTxDSOut)) return false; + } + + return true; +} + +bool CPrivateSendServer::IsAcceptableDenomAndCollateral(int nDenom, CTransaction txCollateral, PoolMessage& nMessageIDRet) +{ + if(!fMasterNode) return false; + + // is denom even smth legit? + std::vector vecBits; + if(!GetDenominationsBits(nDenom, vecBits)) { + LogPrint("privatesend", "CPrivateSendServer::IsAcceptableDenomAndCollateral -- denom not valid!\n"); + nMessageIDRet = ERR_DENOM; + return false; + } + + // check collateral + if(!fUnitTest && !IsCollateralValid(txCollateral)) { + LogPrint("privatesend", "CPrivateSendServer::IsAcceptableDenomAndCollateral -- collateral not valid!\n"); + nMessageIDRet = ERR_INVALID_COLLATERAL; + return false; + } + + return true; +} + +bool CPrivateSendServer::CreateNewSession(int nDenom, CTransaction txCollateral, PoolMessage& nMessageIDRet) +{ + if(!fMasterNode || nSessionID != 0) return false; + + // new session can only be started in idle mode + if(nState != POOL_STATE_IDLE) { + nMessageIDRet = ERR_MODE; + LogPrintf("CPrivateSendServer::CreateNewSession -- incompatible mode: nState=%d\n", nState); + return false; + } + + if(!IsAcceptableDenomAndCollateral(nDenom, txCollateral, nMessageIDRet)) { + return false; + } + + // start new session + nMessageIDRet = MSG_NOERR; + nSessionID = GetRandInt(999999)+1; + nSessionDenom = nDenom; + + SetState(POOL_STATE_QUEUE); + nTimeLastSuccessfulStep = GetTimeMillis(); + + if(!fUnitTest) { + //broadcast that I'm accepting entries, only if it's the first entry through + CDarksendQueue dsq(nDenom, activeMasternode.vin, GetTime(), false); + LogPrint("privatesend", "CPrivateSendServer::CreateNewSession -- signing and relaying new queue: %s\n", dsq.ToString()); + dsq.Sign(); + dsq.Relay(); + vecDarksendQueue.push_back(dsq); + } + + vecSessionCollaterals.push_back(txCollateral); + LogPrintf("CPrivateSendServer::CreateNewSession -- new session created, nSessionID: %d nSessionDenom: %d (%s) vecSessionCollaterals.size(): %d\n", + nSessionID, nSessionDenom, GetDenominationsToString(nSessionDenom), vecSessionCollaterals.size()); + + return true; +} + +bool CPrivateSendServer::AddUserToExistingSession(int nDenom, CTransaction txCollateral, PoolMessage& nMessageIDRet) +{ + if(!fMasterNode || nSessionID == 0 || IsSessionReady()) return false; + + if(!IsAcceptableDenomAndCollateral(nDenom, txCollateral, nMessageIDRet)) { + return false; + } + + // we only add new users to an existing session when we are in queue mode + if(nState != POOL_STATE_QUEUE) { + nMessageIDRet = ERR_MODE; + LogPrintf("CPrivateSendServer::AddUserToExistingSession -- incompatible mode: nState=%d\n", nState); + return false; + } + + if(nDenom != nSessionDenom) { + LogPrintf("CPrivateSendServer::AddUserToExistingSession -- incompatible denom %d (%s) != nSessionDenom %d (%s)\n", + nDenom, GetDenominationsToString(nDenom), nSessionDenom, GetDenominationsToString(nSessionDenom)); + nMessageIDRet = ERR_DENOM; + return false; + } + + // count new user as accepted to an existing session + + nMessageIDRet = MSG_NOERR; + nTimeLastSuccessfulStep = GetTimeMillis(); + vecSessionCollaterals.push_back(txCollateral); + + LogPrintf("CPrivateSendServer::AddUserToExistingSession -- new user accepted, nSessionID: %d nSessionDenom: %d (%s) vecSessionCollaterals.size(): %d\n", + nSessionID, nSessionDenom, GetDenominationsToString(nSessionDenom), vecSessionCollaterals.size()); + + return true; +} + +void CPrivateSendServer::RelayFinalTransaction(const CTransaction& txFinal) +{ + LOCK(cs_vNodes); + BOOST_FOREACH(CNode* pnode, vNodes) + if(pnode->nVersion >= MIN_PRIVATESEND_PEER_PROTO_VERSION) + pnode->PushMessage(NetMsgType::DSFINALTX, nSessionID, txFinal); +} + +void CPrivateSendServer::PushStatus(CNode* pnode, PoolStatusUpdate nStatusUpdate, PoolMessage nMessageID) +{ + if(!pnode) return; + pnode->PushMessage(NetMsgType::DSSTATUSUPDATE, nSessionID, (int)nState, (int)vecEntries.size(), (int)nStatusUpdate, (int)nMessageID); +} + +void CPrivateSendServer::RelayStatus(PoolStatusUpdate nStatusUpdate, PoolMessage nMessageID) +{ + LOCK(cs_vNodes); + BOOST_FOREACH(CNode* pnode, vNodes) + if(pnode->nVersion >= MIN_PRIVATESEND_PEER_PROTO_VERSION) + PushStatus(pnode, nStatusUpdate, nMessageID); +} + +void CPrivateSendServer::RelayCompletedTransaction(PoolMessage nMessageID) +{ + LOCK(cs_vNodes); + BOOST_FOREACH(CNode* pnode, vNodes) + if(pnode->nVersion >= MIN_PRIVATESEND_PEER_PROTO_VERSION) + pnode->PushMessage(NetMsgType::DSCOMPLETE, nSessionID, (int)nMessageID); +} + +void CPrivateSendServer::SetState(PoolState nStateNew) +{ + if(fMasterNode && (nStateNew == POOL_STATE_ERROR || nStateNew == POOL_STATE_SUCCESS)) { + LogPrint("privatesend", "CPrivateSendServer::SetState -- Can't set state to ERROR or SUCCESS as a Masternode. \n"); + return; + } + + LogPrintf("CPrivateSendServer::SetState -- nState: %d, nStateNew: %d\n", nState, nStateNew); + nState = nStateNew; +} + +//TODO: Rename/move to core +void ThreadCheckPrivateSendServer() +{ + 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-server"); + + unsigned int nTick = 0; + + while (true) + { + MilliSleep(1000); + + if(masternodeSync.IsBlockchainSynced() && !ShutdownRequested()) { + nTick++; + privateSendServer.CheckTimeout(); + privateSendServer.CheckForCompleteQueue(); + } + } +} diff --git a/src/privatesend-server.h b/src/privatesend-server.h new file mode 100644 index 0000000000000..9b68d0bbd07b2 --- /dev/null +++ b/src/privatesend-server.h @@ -0,0 +1,82 @@ +// 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. + +#ifndef PRIVATESENDSERVER_H +#define PRIVATESENDSERVER_H + +#include "net.h" +#include "privatesend.h" + +class CPrivateSendServer; + +// The main object for accessing mixing +extern CPrivateSendServer privateSendServer; + +/** Used to keep track of current status of mixing pool + */ +class CPrivateSendServer : public CPrivateSend +{ +private: + mutable CCriticalSection cs_darksend; + + // Mixing uses collateral transactions to trust parties entering the pool + // to behave honestly. If they don't it takes their money. + std::vector vecSessionCollaterals; + + bool fUnitTest; + + /// Add a clients entry to the pool + bool AddEntry(const CDarkSendEntry& entryNew, PoolMessage& nMessageIDRet); + /// Add signature to a txin + bool AddScriptSig(const CTxIn& txin); + + /// Charge fees to bad actors (Charge clients a fee if they're abusive) + void ChargeFees(); + /// Rarely charge fees to pay miners + void ChargeRandomFees(); + + /// Check for process + void CheckPool(); + + void CreateFinalTransaction(); + void CommitFinalTransaction(); + + /// Is this nDenom and txCollateral acceptable? + bool IsAcceptableDenomAndCollateral(int nDenom, CTransaction txCollateral, PoolMessage &nMessageIDRet); + bool CreateNewSession(int nDenom, CTransaction txCollateral, PoolMessage &nMessageIDRet); + bool AddUserToExistingSession(int nDenom, CTransaction txCollateral, PoolMessage &nMessageIDRet); + /// Do we have enough users to take entries? + bool IsSessionReady() { return (int)vecSessionCollaterals.size() >= GetMaxPoolTransactions(); } + + /// Check that all inputs are signed. (Are all inputs signed?) + bool IsSignaturesComplete(); + /// Check to make sure a given input matches an input in the pool and its scriptSig is valid + bool IsInputScriptSigValid(const CTxIn& txin); + /// Are these outputs compatible with other client in the pool? + bool IsOutputsCompatibleWithSessionDenom(const std::vector& vecTxDSOut); + + // Set the 'state' value, with some logging and capturing when the state changed + void SetState(PoolState nStateNew); + + /// Relay mixing Messages + void RelayFinalTransaction(const CTransaction& txFinal); + void PushStatus(CNode* pnode, PoolStatusUpdate nStatusUpdate, PoolMessage nMessageID); + void RelayStatus(PoolStatusUpdate nStatusUpdate, PoolMessage nMessageID = MSG_NOERR); + void RelayCompletedTransaction(PoolMessage nMessageID); + + void SetNull(); + +public: + CPrivateSendServer() : + fUnitTest(false) { SetNull(); } + + void ProcessMessage(CNode* pfrom, std::string& strCommand, CDataStream& vRecv); + + void CheckTimeout(); + void CheckForCompleteQueue(); +}; + +void ThreadCheckPrivateSendServer(); + +#endif diff --git a/src/privatesend.cpp b/src/privatesend.cpp new file mode 100644 index 0000000000000..c5e1c8001b286 --- /dev/null +++ b/src/privatesend.cpp @@ -0,0 +1,416 @@ +// 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.h" + +#include "activemasternode.h" +#include "consensus/validation.h" +#include "governance.h" +#include "init.h" +#include "instantx.h" +#include "masternode-payments.h" +#include "masternode-sync.h" +#include "masternodeman.h" +#include "messagesigner.h" +#include "script/sign.h" +#include "txmempool.h" +#include "util.h" +#include "utilmoneystr.h" + +#include + +std::map mapDarksendBroadcastTxes; +std::vector vecPrivateSendDenominations; + +CDarkSendEntry::CDarkSendEntry(const std::vector& vecTxIn, const std::vector& vecTxOut, const CTransaction& txCollateral) : + txCollateral(txCollateral) +{ + BOOST_FOREACH(CTxIn txin, vecTxIn) + vecTxDSIn.push_back(txin); + BOOST_FOREACH(CTxOut txout, vecTxOut) + vecTxDSOut.push_back(txout); +} + +bool CDarkSendEntry::AddScriptSig(const CTxIn& txin) +{ + BOOST_FOREACH(CTxDSIn& txdsin, vecTxDSIn) { + if(txdsin.prevout == txin.prevout && txdsin.nSequence == txin.nSequence) { + if(txdsin.fHasSig) return false; + + txdsin.scriptSig = txin.scriptSig; + txdsin.prevPubKey = txin.prevPubKey; + txdsin.fHasSig = true; + + return true; + } + } + + return false; +} + +void CPrivateSend::InitDenominations() +{ + vecPrivateSendDenominations.clear(); + /* Denominations + + A note about convertability. Within mixing pools, each denomination + is convertable to another. + + For example: + 1DRK+1000 == (.1DRK+100)*10 + 10DRK+10000 == (1DRK+1000)*10 + */ + /* Disabled + vecPrivateSendDenominations.push_back( (100 * COIN)+100000 ); + */ + vecPrivateSendDenominations.push_back( (10 * COIN)+10000 ); + vecPrivateSendDenominations.push_back( (1 * COIN)+1000 ); + vecPrivateSendDenominations.push_back( (.1 * COIN)+100 ); + vecPrivateSendDenominations.push_back( (.01 * COIN)+10 ); + /* Disabled till we need them + vecPrivateSendDenominations.push_back( (.001 * COIN)+1 ); + */ +} + +void CPrivateSend::SetNull() +{ + // Both sides + nState = POOL_STATE_IDLE; + nSessionID = 0; + nSessionDenom = 0; + vecEntries.clear(); + finalMutableTransaction.vin.clear(); + finalMutableTransaction.vout.clear(); + nTimeLastSuccessfulStep = GetTimeMillis(); +} + +std::string CPrivateSend::GetStateString() const +{ + switch(nState) { + case POOL_STATE_IDLE: return "IDLE"; + case POOL_STATE_QUEUE: return "QUEUE"; + case POOL_STATE_ACCEPTING_ENTRIES: return "ACCEPTING_ENTRIES"; + case POOL_STATE_SIGNING: return "SIGNING"; + case POOL_STATE_ERROR: return "ERROR"; + case POOL_STATE_SUCCESS: return "SUCCESS"; + default: return "UNKNOWN"; + } +} + +// check to make sure the collateral provided by the client is valid +bool CPrivateSend::IsCollateralValid(const CTransaction& txCollateral) +{ + if(txCollateral.vout.empty()) return false; + if(txCollateral.nLockTime != 0) return false; + + CAmount nValueIn = 0; + CAmount nValueOut = 0; + bool fMissingTx = false; + + BOOST_FOREACH(const CTxOut txout, txCollateral.vout) { + nValueOut += txout.nValue; + + if(!txout.scriptPubKey.IsNormalPaymentScript()) { + LogPrintf ("CPrivateSend::IsCollateralValid -- Invalid Script, txCollateral=%s", txCollateral.ToString()); + return false; + } + } + + BOOST_FOREACH(const CTxIn txin, txCollateral.vin) { + CTransaction txPrev; + uint256 hash; + if(GetTransaction(txin.prevout.hash, txPrev, Params().GetConsensus(), hash, true)) { + if(txPrev.vout.size() > txin.prevout.n) + nValueIn += txPrev.vout[txin.prevout.n].nValue; + } else { + fMissingTx = true; + } + } + + if(fMissingTx) { + LogPrint("privatesend", "CPrivateSend::IsCollateralValid -- Unknown inputs in collateral transaction, txCollateral=%s", txCollateral.ToString()); + return false; + } + + //collateral transactions are required to pay out PRIVATESEND_COLLATERAL as a fee to the miners + if(nValueIn - nValueOut < PRIVATESEND_COLLATERAL) { + LogPrint("privatesend", "CPrivateSend::IsCollateralValid -- did not include enough fees in transaction: fees: %d, txCollateral=%s", nValueOut - nValueIn, txCollateral.ToString()); + return false; + } + + LogPrint("privatesend", "CPrivateSend::IsCollateralValid -- %s", txCollateral.ToString()); + + { + LOCK(cs_main); + CValidationState validationState; + if(!AcceptToMemoryPool(mempool, validationState, txCollateral, false, NULL, false, true, true)) { + LogPrint("privatesend", "CPrivateSend::IsCollateralValid -- didn't pass AcceptToMemoryPool()\n"); + return false; + } + } + + return true; +} + +/* Create a nice string to show the denominations + Function returns as follows (for 4 denominations): + ( bit on if present ) + bit 0 - 100 + bit 1 - 10 + bit 2 - 1 + bit 3 - .1 + bit 4 and so on - out-of-bounds + none of above - non-denom +*/ +std::string CPrivateSend::GetDenominationsToString(int nDenom) +{ + std::string strDenom = ""; + int nMaxDenoms = vecPrivateSendDenominations.size(); + + if(nDenom >= (1 << nMaxDenoms)) { + return "out-of-bounds"; + } + + for (int i = 0; i < nMaxDenoms; ++i) { + if(nDenom & (1 << i)) { + strDenom += (strDenom.empty() ? "" : "+") + FormatMoney(vecPrivateSendDenominations[i]); + } + } + + if(strDenom.empty()) { + return "non-denom"; + } + + return strDenom; +} + +int CPrivateSend::GetDenominations(const std::vector& vecTxDSOut) +{ + std::vector vecTxOut; + + BOOST_FOREACH(CTxDSOut out, vecTxDSOut) + vecTxOut.push_back(out); + + return GetDenominations(vecTxOut); +} + +/* Return a bitshifted integer representing the denominations in this list + Function returns as follows (for 4 denominations): + ( bit on if present ) + 100 - bit 0 + 10 - bit 1 + 1 - bit 2 + .1 - bit 3 + non-denom - 0, all bits off +*/ +int CPrivateSend::GetDenominations(const std::vector& vecTxOut, bool fSingleRandomDenom) +{ + std::vector > vecDenomUsed; + + // make a list of denominations, with zero uses + BOOST_FOREACH(CAmount nDenomValue, vecPrivateSendDenominations) + vecDenomUsed.push_back(std::make_pair(nDenomValue, 0)); + + // look for denominations and update uses to 1 + BOOST_FOREACH(CTxOut txout, vecTxOut) { + bool found = false; + BOOST_FOREACH (PAIRTYPE(CAmount, int)& s, vecDenomUsed) { + if(txout.nValue == s.first) { + s.second = 1; + found = true; + } + } + if(!found) return 0; + } + + int nDenom = 0; + int c = 0; + // if the denomination is used, shift the bit on + BOOST_FOREACH (PAIRTYPE(CAmount, int)& s, vecDenomUsed) { + int bit = (fSingleRandomDenom ? GetRandInt(2) : 1) & s.second; + nDenom |= bit << c++; + if(fSingleRandomDenom && bit) break; // use just one random denomination + } + + return nDenom; +} + +bool CPrivateSend::GetDenominationsBits(int nDenom, std::vector &vecBitsRet) +{ + // ( bit on if present, 4 denominations example ) + // bit 0 - 100DASH+1 + // bit 1 - 10DASH+1 + // bit 2 - 1DASH+1 + // bit 3 - .1DASH+1 + + int nMaxDenoms = vecPrivateSendDenominations.size(); + + if(nDenom >= (1 << nMaxDenoms)) return false; + + vecBitsRet.clear(); + + for (int i = 0; i < nMaxDenoms; ++i) { + if(nDenom & (1 << i)) { + vecBitsRet.push_back(i); + } + } + + return !vecBitsRet.empty(); +} + +int CPrivateSend::GetDenominationsByAmounts(const std::vector& vecAmount) +{ + CScript scriptTmp = CScript(); + std::vector vecTxOut; + + BOOST_REVERSE_FOREACH(CAmount nAmount, vecAmount) { + CTxOut txout(nAmount, scriptTmp); + vecTxOut.push_back(txout); + } + + return GetDenominations(vecTxOut, true); +} + +std::string CPrivateSend::GetMessageByID(PoolMessage nMessageID) +{ + switch (nMessageID) { + case ERR_ALREADY_HAVE: return _("Already have that input."); + case ERR_DENOM: return _("No matching denominations found for mixing."); + case ERR_ENTRIES_FULL: return _("Entries are full."); + case ERR_EXISTING_TX: return _("Not compatible with existing transactions."); + case ERR_FEES: return _("Transaction fees are too high."); + case ERR_INVALID_COLLATERAL: return _("Collateral not valid."); + case ERR_INVALID_INPUT: return _("Input is not valid."); + case ERR_INVALID_SCRIPT: return _("Invalid script detected."); + case ERR_INVALID_TX: return _("Transaction not valid."); + case ERR_MAXIMUM: return _("Entry exceeds maximum size."); + case ERR_MN_LIST: return _("Not in the Masternode list."); + case ERR_MODE: return _("Incompatible mode."); + case ERR_NON_STANDARD_PUBKEY: return _("Non-standard public key detected."); + case ERR_NOT_A_MN: return _("This is not a Masternode."); // not used + case ERR_QUEUE_FULL: return _("Masternode queue is full."); + case ERR_RECENT: return _("Last PrivateSend was too recent."); + case ERR_SESSION: return _("Session not complete!"); + case ERR_MISSING_TX: return _("Missing input transaction information."); + case ERR_VERSION: return _("Incompatible version."); + case MSG_NOERR: return _("No errors detected."); + case MSG_SUCCESS: return _("Transaction created successfully."); + case MSG_ENTRIES_ADDED: return _("Your entries added successfully."); + default: return _("Unknown response."); + } +} + +bool CDarksendQueue::Sign() +{ + if(!fMasterNode) return false; + + std::string strMessage = vin.ToString() + boost::lexical_cast(nDenom) + boost::lexical_cast(nTime) + boost::lexical_cast(fReady); + + if(!CMessageSigner::SignMessage(strMessage, vchSig, activeMasternode.keyMasternode)) { + LogPrintf("CDarksendQueue::Sign -- SignMessage() failed, %s\n", ToString()); + return false; + } + + return CheckSignature(activeMasternode.pubKeyMasternode); +} + +bool CDarksendQueue::CheckSignature(const CPubKey& pubKeyMasternode) +{ + std::string strMessage = vin.ToString() + boost::lexical_cast(nDenom) + boost::lexical_cast(nTime) + boost::lexical_cast(fReady); + std::string strError = ""; + + if(!CMessageSigner::VerifyMessage(pubKeyMasternode, vchSig, strMessage, strError)) { + LogPrintf("CDarksendQueue::CheckSignature -- Got bad Masternode queue signature: %s; error: %s\n", ToString(), strError); + return false; + } + + return true; +} + +bool CDarksendQueue::Relay() +{ + std::vector vNodesCopy = CopyNodeVector(); + BOOST_FOREACH(CNode* pnode, vNodesCopy) + if(pnode->nVersion >= MIN_PRIVATESEND_PEER_PROTO_VERSION) + pnode->PushMessage(NetMsgType::DSQUEUE, (*this)); + + ReleaseNodeVector(vNodesCopy); + return true; +} + +bool CDarksendBroadcastTx::Sign() +{ + if(!fMasterNode) return false; + + std::string strMessage = tx.GetHash().ToString() + boost::lexical_cast(sigTime); + + if(!CMessageSigner::SignMessage(strMessage, vchSig, activeMasternode.keyMasternode)) { + LogPrintf("CDarksendBroadcastTx::Sign -- SignMessage() failed\n"); + return false; + } + + return CheckSignature(activeMasternode.pubKeyMasternode); +} + +bool CDarksendBroadcastTx::CheckSignature(const CPubKey& pubKeyMasternode) +{ + std::string strMessage = tx.GetHash().ToString() + boost::lexical_cast(sigTime); + std::string strError = ""; + + if(!CMessageSigner::VerifyMessage(pubKeyMasternode, vchSig, strMessage, strError)) { + LogPrintf("CDarksendBroadcastTx::CheckSignature -- Got bad dstx signature, error: %s\n", strError); + return false; + } + + return true; +} + +//TODO: Rename/move to core +void ThreadCheckPrivateSend() +{ + 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"); + + unsigned int nTick = 0; + + while (true) + { + MilliSleep(1000); + + // try to sync from all available nodes, one step at a time + masternodeSync.ProcessTick(); + + if(masternodeSync.IsBlockchainSynced() && !ShutdownRequested()) { + + nTick++; + + // make sure to check all masternodes first + mnodeman.Check(); + + // check if we should activate or ping every few minutes, + // slightly postpone first run to give net thread a chance to connect to some peers + if(nTick % MASTERNODE_MIN_MNP_SECONDS == 15) + activeMasternode.ManageState(); + + if(nTick % 60 == 0) { + mnodeman.ProcessMasternodeConnections(); + mnodeman.CheckAndRemove(); + mnpayments.CheckAndRemove(); + instantsend.CheckAndRemove(); + } + if(fMasterNode && (nTick % (60 * 5) == 0)) { + mnodeman.DoFullVerificationStep(); + } + + if(nTick % (60 * 5) == 0) { + governance.DoMaintenance(); + } + } + } +} diff --git a/src/darksend.h b/src/privatesend.h similarity index 52% rename from src/darksend.h rename to src/privatesend.h index 0f990a305bcac..6cc0f68bfb3b0 100644 --- a/src/darksend.h +++ b/src/privatesend.h @@ -2,14 +2,16 @@ // Distributed under the MIT/X11 software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. -#ifndef DARKSEND_H -#define DARKSEND_H +#ifndef PRIVATESEND_H +#define PRIVATESEND_H -#include "masternode.h" -#include "wallet/wallet.h" +#include "chainparams.h" +#include "primitives/transaction.h" +#include "pubkey.h" +#include "utiltime.h" +#include "tinyformat.h" -class CDarksendPool; -class CDarksendBroadcastTx; +class CPrivateSend; // timeouts static const int PRIVATESEND_AUTO_TIMEOUT_MIN = 5; @@ -22,27 +24,11 @@ static const int MIN_PRIVATESEND_PEER_PROTO_VERSION = 70206; static const CAmount PRIVATESEND_COLLATERAL = 0.001 * COIN; static const CAmount PRIVATESEND_ENTRY_MAX_SIZE = 9; -static const int DENOMS_COUNT_MAX = 100; - -static const int DEFAULT_PRIVATESEND_ROUNDS = 2; -static const int DEFAULT_PRIVATESEND_AMOUNT = 1000; -static const int DEFAULT_PRIVATESEND_LIQUIDITY = 0; -static const bool DEFAULT_PRIVATESEND_MULTISESSION = false; -// Warn user if mixing in gui or try to create backup if mixing in daemon mode -// when we have only this many keys left -static const int PRIVATESEND_KEYS_THRESHOLD_WARNING = 100; -// Stop mixing completely, it's too dangerous to continue when we have only this many keys left -static const int PRIVATESEND_KEYS_THRESHOLD_STOP = 50; - -extern int nPrivateSendRounds; -extern int nPrivateSendAmount; -extern int nLiquidityProvider; -extern bool fEnablePrivateSend; -extern bool fPrivateSendMultiSession; +class CDarksendBroadcastTx; // The main object for accessing mixing -extern CDarksendPool darkSendPool; +// extern CPrivateSend privateSend; extern std::map mapDarksendBroadcastTxes; extern std::vector vecPrivateSendDenominations; @@ -100,14 +86,7 @@ class CDarkSendEntry txCollateral(CTransaction()) {} - CDarkSendEntry(const std::vector& vecTxIn, const std::vector& vecTxOut, const CTransaction& txCollateral) : - txCollateral(txCollateral) - { - BOOST_FOREACH(CTxIn txin, vecTxIn) - vecTxDSIn.push_back(txin); - BOOST_FOREACH(CTxOut txout, vecTxOut) - vecTxDSOut.push_back(txout); - } + CDarkSendEntry(const std::vector& vecTxIn, const std::vector& vecTxOut, const CTransaction& txCollateral); ADD_SERIALIZE_METHODS; @@ -231,11 +210,10 @@ class CDarksendBroadcastTx bool CheckSignature(const CPubKey& pubKeyMasternode); }; -/** Used to keep track of current status of mixing pool - */ -class CDarksendPool +// static class or base class or helper? +class CPrivateSend { -private: +protected: // pool responses enum PoolMessage { ERR_ALREADY_HAVE, @@ -251,7 +229,7 @@ class CDarksendPool ERR_MN_LIST, ERR_MODE, ERR_NON_STANDARD_PUBKEY, - ERR_NOT_A_MN, + ERR_NOT_A_MN, // not used ERR_QUEUE_FULL, ERR_RECENT, ERR_SESSION, @@ -282,58 +260,18 @@ class CDarksendPool STATUS_ACCEPTED }; - mutable CCriticalSection cs_darksend; - // The current mixing sessions in progress on the network std::vector vecDarksendQueue; - // Keep track of the used Masternodes - std::vector vecMasternodesUsed; - - std::vector vecDenominationsSkipped; - std::vector vecOutPointLocked; - // Mixing uses collateral transactions to trust parties entering the pool - // to behave honestly. If they don't it takes their money. - std::vector vecSessionCollaterals; + std::vector vecEntries; // Masternode/clients entries PoolState nState; // should be one of the POOL_STATE_XXX values int64_t nTimeLastSuccessfulStep; // the time when last successful mixing step was performed, in UTC milliseconds - int nCachedLastSuccessBlock; - int nMinBlockSpacing; //required blocks between mixes - const CBlockIndex *pCurrentBlockIndex; // Keep track of current block index - int nSessionID; // 0 if no mixing session is active - int nEntriesCount; - bool fLastEntryAccepted; - - std::string strLastMessage; - std::string strAutoDenomResult; - - bool fUnitTest; - - CMutableTransaction txMyCollateral; // client side collateral CMutableTransaction finalMutableTransaction; // the finalized transaction ready for signing - /// Add a clients entry to the pool - bool AddEntry(const CDarkSendEntry& entryNew, PoolMessage& nMessageIDRet); - /// Add signature to a txin - bool AddScriptSig(const CTxIn& txin); - - /// Charge fees to bad actors (Charge clients a fee if they're abusive) - void ChargeFees(); - /// Rarely charge fees to pay miners - void ChargeRandomFees(); - - /// Check for process - void CheckPool(); - - void CreateFinalTransaction(); - void CommitFinalTransaction(); - - void CompletedTransaction(PoolMessage nMessageID); - /// Get the denominations for a specific amount of dash. int GetDenominationsByAmounts(const std::vector& vecAmount); @@ -342,92 +280,17 @@ class CDarksendPool /// Get the maximum number of transactions for the pool int GetMaxPoolTransactions() { return Params().PoolMaxTransactions(); } - /// Is this nDenom and txCollateral acceptable? - bool IsAcceptableDenomAndCollateral(int nDenom, CTransaction txCollateral, PoolMessage &nMessageIDRet); - bool CreateNewSession(int nDenom, CTransaction txCollateral, PoolMessage &nMessageIDRet); - bool AddUserToExistingSession(int nDenom, CTransaction txCollateral, PoolMessage &nMessageIDRet); - /// Do we have enough users to take entries? - bool IsSessionReady() { return (int)vecSessionCollaterals.size() >= GetMaxPoolTransactions(); } - /// If the collateral is valid given by a client bool IsCollateralValid(const CTransaction& txCollateral); - /// Check that all inputs are signed. (Are all inputs signed?) - bool IsSignaturesComplete(); - /// Check to make sure a given input matches an input in the pool and its scriptSig is valid - bool IsInputScriptSigValid(const CTxIn& txin); - /// Are these outputs compatible with other client in the pool? - bool IsOutputsCompatibleWithSessionDenom(const std::vector& vecTxDSOut); - - bool IsDenomSkipped(CAmount nDenomValue) { - return std::find(vecDenominationsSkipped.begin(), vecDenominationsSkipped.end(), nDenomValue) != vecDenominationsSkipped.end(); - } - - /// Create denominations - bool CreateDenominated(); - bool CreateDenominated(const CompactTallyItem& tallyItem, bool fCreateMixingCollaterals); - - /// Split up large inputs or make fee sized inputs - bool MakeCollateralAmounts(); - bool MakeCollateralAmounts(const CompactTallyItem& tallyItem); - - /// As a client, submit part of a future mixing transaction to a Masternode to start the process - bool SubmitDenominate(); - /// step 1: prepare denominated inputs and outputs - bool PrepareDenominate(int nMinRounds, int nMaxRounds, std::string& strErrorRet, std::vector& vecTxInRet, std::vector& vecTxOutRet); - /// step 2: send denominated inputs and outputs prepared in step 1 - bool SendDenominate(const std::vector& vecTxIn, const std::vector& vecTxOut); - - /// Get Masternode updates about the progress of mixing - bool CheckPoolStateUpdate(PoolState nStateNew, int nEntriesCountNew, PoolStatusUpdate nStatusUpdate, PoolMessage nMessageID, int nSessionIDNew=0); - // Set the 'state' value, with some logging and capturing when the state changed - void SetState(PoolState nStateNew); - - /// As a client, check and sign the final transaction - bool SignFinalTransaction(const CTransaction& finalTransactionNew, CNode* pnode); - - /// Relay mixing Messages - void RelayFinalTransaction(const CTransaction& txFinal); - void RelaySignaturesAnon(std::vector& vin); - void RelayInAnon(std::vector& vin, std::vector& vout); - void RelayIn(const CDarkSendEntry& entry); - void PushStatus(CNode* pnode, PoolStatusUpdate nStatusUpdate, PoolMessage nMessageID); - void RelayStatus(PoolStatusUpdate nStatusUpdate, PoolMessage nMessageID = MSG_NOERR); - void RelayCompletedTransaction(PoolMessage nMessageID); void SetNull(); public: - CMasternode* pSubmittedToMasternode; int nSessionDenom; //Users must submit an denom matching this - int nCachedNumBlocks; //used for the overview screen - bool fCreateAutoBackups; //builtin support for automatic backups - - CDarksendPool() : - nCachedLastSuccessBlock(0), - nMinBlockSpacing(0), - fUnitTest(false), - txMyCollateral(CMutableTransaction()), - nCachedNumBlocks(std::numeric_limits::max()), - fCreateAutoBackups(true) { SetNull(); } - - /** Process a mixing message using the protocol below - * \param pfrom - * \param strCommand lower case command string; valid values are: - * Command | Description - * -------- | ----------------- - * dsa | Acceptable - * dsc | Complete - * dsf | Final tx - * dsi | Vector of CTxIn - * dsq | Queue - * dss | Signal Final Tx - * dssu | status update - * \param vRecv - */ - void ProcessMessage(CNode* pfrom, std::string& strCommand, CDataStream& vRecv); + + CPrivateSend() { SetNull(); } void InitDenominations(); - void ClearSkippedDenominations() { vecDenominationsSkipped.clear(); } /// Get the denominations for a list of outputs (returns a bitshifted integer) int GetDenominations(const std::vector& vecTxOut, bool fSingleRandomDenom = false); @@ -437,31 +300,13 @@ class CDarksendPool CAmount GetMaxPoolAmount() { return vecPrivateSendDenominations.empty() ? 0 : PRIVATESEND_ENTRY_MAX_SIZE * vecPrivateSendDenominations.front(); } - void SetMinBlockSpacing(int nMinBlockSpacingIn) { nMinBlockSpacing = nMinBlockSpacingIn; } - - void ResetPool(); - - void UnlockCoins(); - int GetQueueSize() const { return vecDarksendQueue.size(); } int GetState() const { return nState; } std::string GetStateString() const; - std::string GetStatus(); int GetEntriesCount() const { return vecEntries.size(); } - - /// Passively run mixing in the background according to the configuration in settings - bool DoAutomaticDenominating(bool fDryRun=false); - - void CheckTimeout(); - void CheckForCompleteQueue(); - - /// Process a new block - void NewBlock(); - - void UpdatedBlockTip(const CBlockIndex *pindex); }; -void ThreadCheckDarkSendPool(); +void ThreadCheckPrivateSend(); #endif diff --git a/src/qt/clientmodel.cpp b/src/qt/clientmodel.cpp index 0bcefc295fab9..1d2009e15856e 100644 --- a/src/qt/clientmodel.cpp +++ b/src/qt/clientmodel.cpp @@ -18,9 +18,9 @@ #include "ui_interface.h" #include "util.h" -#include "darksend.h" #include "masternodeman.h" #include "masternode-sync.h" +#include "privatesend-client.h" #include diff --git a/src/qt/coincontroldialog.cpp b/src/qt/coincontroldialog.cpp index c58c5b48a96db..9f5f50e0c5d86 100644 --- a/src/qt/coincontroldialog.cpp +++ b/src/qt/coincontroldialog.cpp @@ -19,8 +19,8 @@ #include "main.h" // For minRelayTxFee #include "wallet/wallet.h" -#include "darksend.h" #include "instantx.h" +#include "privatesend-client.h" #include // for 'map_list_of()' @@ -451,7 +451,7 @@ void CoinControlDialog::viewItemChanged(QTreeWidgetItem* item, int column) coinControl->Select(outpt); CTxIn txin = CTxIn(outpt); int nRounds = pwalletMain->GetInputPrivateSendRounds(txin); - if (coinControl->fUsePrivateSend && nRounds < nPrivateSendRounds) { + if (coinControl->fUsePrivateSend && nRounds < privateSendClient.nPrivateSendRounds) { QMessageBox::warning(this, windowTitle(), tr("Non-anonymized input selected. PrivateSend will be disabled.

If you still want to use PrivateSend, please deselect all non-nonymized inputs first and then check PrivateSend checkbox again."), QMessageBox::Ok, QMessageBox::Ok); diff --git a/src/qt/darksendconfig.cpp b/src/qt/darksendconfig.cpp index 9055cd0145bc3..ceb7ec3f79ac5 100644 --- a/src/qt/darksendconfig.cpp +++ b/src/qt/darksendconfig.cpp @@ -2,9 +2,9 @@ #include "ui_darksendconfig.h" #include "bitcoinunits.h" -#include "darksend.h" #include "guiconstants.h" #include "optionsmodel.h" +#include "privatesend-client.h" #include "walletmodel.h" #include @@ -86,6 +86,6 @@ void DarksendConfig::configure(bool enabled, int coins, int rounds) { settings.setValue("nPrivateSendRounds", rounds); settings.setValue("nPrivateSendAmount", coins); - nPrivateSendRounds = rounds; - nPrivateSendAmount = coins; + privateSendClient.nPrivateSendRounds = rounds; + privateSendClient.nPrivateSendAmount = coins; } diff --git a/src/qt/optionsdialog.cpp b/src/qt/optionsdialog.cpp index e18e5c3859fe0..443ee121629bd 100644 --- a/src/qt/optionsdialog.cpp +++ b/src/qt/optionsdialog.cpp @@ -21,7 +21,7 @@ #include "wallet/wallet.h" // for CWallet::GetRequiredFee() #endif -#include "darksend.h" +#include "privatesend-client.h" #include @@ -260,7 +260,7 @@ void OptionsDialog::on_resetButton_clicked() void OptionsDialog::on_okButton_clicked() { mapper->submit(); - darkSendPool.nCachedNumBlocks = std::numeric_limits::max(); + privateSendClient.nCachedNumBlocks = std::numeric_limits::max(); pwalletMain->MarkDirty(); accept(); updateDefaultProxyNets(); diff --git a/src/qt/optionsmodel.cpp b/src/qt/optionsmodel.cpp index f22089b3157f5..e82155a2aac07 100644 --- a/src/qt/optionsmodel.cpp +++ b/src/qt/optionsmodel.cpp @@ -23,10 +23,10 @@ #include "wallet/walletdb.h" #endif -#include "darksend.h" #ifdef ENABLE_WALLET #include "masternodeconfig.h" #endif +#include "privatesend-client.h" #include #include @@ -126,7 +126,7 @@ void OptionsModel::Init(bool resetSettings) settings.setValue("nPrivateSendRounds", DEFAULT_PRIVATESEND_ROUNDS); if (!SoftSetArg("-privatesendrounds", settings.value("nPrivateSendRounds").toString().toStdString())) addOverriddenOption("-privatesendrounds"); - nPrivateSendRounds = settings.value("nPrivateSendRounds").toInt(); + privateSendClient.nPrivateSendRounds = settings.value("nPrivateSendRounds").toInt(); if (!settings.contains("nPrivateSendAmount")) { // for migration from old settings @@ -137,13 +137,13 @@ void OptionsModel::Init(bool resetSettings) } if (!SoftSetArg("-privatesendamount", settings.value("nPrivateSendAmount").toString().toStdString())) addOverriddenOption("-privatesendamount"); - nPrivateSendAmount = settings.value("nPrivateSendAmount").toInt(); + privateSendClient.nPrivateSendAmount = settings.value("nPrivateSendAmount").toInt(); if (!settings.contains("fPrivateSendMultiSession")) settings.setValue("fPrivateSendMultiSession", DEFAULT_PRIVATESEND_MULTISESSION); if (!SoftSetBoolArg("-privatesendmultisession", settings.value("fPrivateSendMultiSession").toBool())) addOverriddenOption("-privatesendmultisession"); - fPrivateSendMultiSession = settings.value("fPrivateSendMultiSession").toBool(); + privateSendClient.fPrivateSendMultiSession = settings.value("fPrivateSendMultiSession").toBool(); #endif // Network @@ -407,24 +407,24 @@ bool OptionsModel::setData(const QModelIndex & index, const QVariant & value, in case PrivateSendRounds: if (settings.value("nPrivateSendRounds") != value) { - nPrivateSendRounds = value.toInt(); - settings.setValue("nPrivateSendRounds", nPrivateSendRounds); + privateSendClient.nPrivateSendRounds = value.toInt(); + settings.setValue("nPrivateSendRounds", privateSendClient.nPrivateSendRounds); Q_EMIT privateSendRoundsChanged(); } break; case PrivateSendAmount: if (settings.value("nPrivateSendAmount") != value) { - nPrivateSendAmount = value.toInt(); - settings.setValue("nPrivateSendAmount", nPrivateSendAmount); + privateSendClient.nPrivateSendAmount = value.toInt(); + settings.setValue("nPrivateSendAmount", privateSendClient.nPrivateSendAmount); Q_EMIT privateSentAmountChanged(); } break; case PrivateSendMultiSession: if (settings.value("fPrivateSendMultiSession") != value) { - fPrivateSendMultiSession = value.toBool(); - settings.setValue("fPrivateSendMultiSession", fPrivateSendMultiSession); + privateSendClient.fPrivateSendMultiSession = value.toBool(); + settings.setValue("fPrivateSendMultiSession", privateSendClient.fPrivateSendMultiSession); } break; #endif diff --git a/src/qt/overviewpage.cpp b/src/qt/overviewpage.cpp index ad88be78ddc1c..0ab5e671165fc 100644 --- a/src/qt/overviewpage.cpp +++ b/src/qt/overviewpage.cpp @@ -18,10 +18,10 @@ #include "utilitydialog.h" #include "walletmodel.h" -#include "darksend.h" #include "instantx.h" #include "darksendconfig.h" #include "masternode-sync.h" +#include "privatesend-client.h" #include #include @@ -168,14 +168,14 @@ OverviewPage::OverviewPage(const PlatformStyle *platformStyle, QWidget *parent) ui->labelPrivateSendEnabled->setToolTip(tr("Automatic backups are disabled, no mixing available!")); } } else { - if(!fEnablePrivateSend){ + if(!privateSendClient.fEnablePrivateSend){ ui->togglePrivateSend->setText(tr("Start Mixing")); } else { ui->togglePrivateSend->setText(tr("Stop Mixing")); } - // Disable darkSendPool builtin support for automatic backups while we are in GUI, + // Disable privateSendClient builtin support for automatic backups while we are in GUI, // we'll handle automatic backups and user warnings in privateSendStatus() - darkSendPool.fCreateAutoBackups = false; + privateSendClient.fCreateAutoBackups = false; timer = new QTimer(this); connect(timer, SIGNAL(timeout()), this, SLOT(privateSendStatus())); @@ -330,7 +330,7 @@ void OverviewPage::updatePrivateSendProgress() if(!pwalletMain) return; QString strAmountAndRounds; - QString strPrivateSendAmount = BitcoinUnits::formatHtmlWithUnit(nDisplayUnit, nPrivateSendAmount * COIN, false, BitcoinUnits::separatorAlways); + QString strPrivateSendAmount = BitcoinUnits::formatHtmlWithUnit(nDisplayUnit, privateSendClient.nPrivateSendAmount * COIN, false, BitcoinUnits::separatorAlways); if(currentBalance == 0) { @@ -339,7 +339,7 @@ void OverviewPage::updatePrivateSendProgress() // when balance is zero just show info from settings strPrivateSendAmount = strPrivateSendAmount.remove(strPrivateSendAmount.indexOf("."), BitcoinUnits::decimals(nDisplayUnit) + 1); - strAmountAndRounds = strPrivateSendAmount + " / " + tr("%n Rounds", "", nPrivateSendRounds); + strAmountAndRounds = strPrivateSendAmount + " / " + tr("%n Rounds", "", privateSendClient.nPrivateSendRounds); ui->labelAmountRounds->setToolTip(tr("No inputs detected")); ui->labelAmountRounds->setText(strAmountAndRounds); @@ -363,15 +363,15 @@ void OverviewPage::updatePrivateSendProgress() CAmount nMaxToAnonymize = nAnonymizableBalance + currentAnonymizedBalance + nDenominatedUnconfirmedBalance; // If it's more than the anon threshold, limit to that. - if(nMaxToAnonymize > nPrivateSendAmount*COIN) nMaxToAnonymize = nPrivateSendAmount*COIN; + if(nMaxToAnonymize > privateSendClient.nPrivateSendAmount*COIN) nMaxToAnonymize = privateSendClient.nPrivateSendAmount*COIN; if(nMaxToAnonymize == 0) return; - if(nMaxToAnonymize >= nPrivateSendAmount * COIN) { + if(nMaxToAnonymize >= privateSendClient.nPrivateSendAmount * COIN) { ui->labelAmountRounds->setToolTip(tr("Found enough compatible inputs to anonymize %1") .arg(strPrivateSendAmount)); strPrivateSendAmount = strPrivateSendAmount.remove(strPrivateSendAmount.indexOf("."), BitcoinUnits::decimals(nDisplayUnit) + 1); - strAmountAndRounds = strPrivateSendAmount + " / " + tr("%n Rounds", "", nPrivateSendRounds); + strAmountAndRounds = strPrivateSendAmount + " / " + tr("%n Rounds", "", privateSendClient.nPrivateSendRounds); } else { QString strMaxToAnonymize = BitcoinUnits::formatHtmlWithUnit(nDisplayUnit, nMaxToAnonymize, false, BitcoinUnits::separatorAlways); ui->labelAmountRounds->setToolTip(tr("Not enough compatible inputs to anonymize %1,
" @@ -381,7 +381,7 @@ void OverviewPage::updatePrivateSendProgress() strMaxToAnonymize = strMaxToAnonymize.remove(strMaxToAnonymize.indexOf("."), BitcoinUnits::decimals(nDisplayUnit) + 1); strAmountAndRounds = "" + QString(BitcoinUnits::factor(nDisplayUnit) == 1 ? "" : "~") + strMaxToAnonymize + - " / " + tr("%n Rounds", "", nPrivateSendRounds) + ""; + " / " + tr("%n Rounds", "", privateSendClient.nPrivateSendRounds) + ""; } ui->labelAmountRounds->setText(strAmountAndRounds); @@ -408,7 +408,7 @@ void OverviewPage::updatePrivateSendProgress() // apply some weights to them ... float denomWeight = 1; - float anonNormWeight = nPrivateSendRounds; + float anonNormWeight = privateSendClient.nPrivateSendRounds; float anonFullWeight = 2; float fullWeight = denomWeight + anonNormWeight + anonFullWeight; // ... and calculate the whole progress @@ -424,7 +424,7 @@ void OverviewPage::updatePrivateSendProgress() tr("Denominated") + ": %2%
" + tr("Mixed") + ": %3%
" + tr("Anonymized") + ": %4%
" + - tr("Denominated inputs have %5 of %n rounds on average", "", nPrivateSendRounds)) + tr("Denominated inputs have %5 of %n rounds on average", "", privateSendClient.nPrivateSendRounds)) .arg(progress).arg(denomPart).arg(anonNormPart).arg(anonFullPart) .arg(nAverageAnonymizedRounds); ui->privateSendProgress->setToolTip(strToolPip); @@ -456,7 +456,7 @@ void OverviewPage::privateSendStatus() int nBestHeight = clientModel->getNumBlocks(); // We are processing more then 1 block per second, we'll just leave - if(((nBestHeight - darkSendPool.nCachedNumBlocks) / (GetTimeMillis() - nLastDSProgressBlockTime + 1) > 1)) return; + if(((nBestHeight - privateSendClient.nCachedNumBlocks) / (GetTimeMillis() - nLastDSProgressBlockTime + 1) > 1)) return; nLastDSProgressBlockTime = GetTimeMillis(); QString strKeysLeftText(tr("keys left: %1").arg(pwalletMain->nKeysLeftSinceAutoBackup)); @@ -465,9 +465,9 @@ void OverviewPage::privateSendStatus() } ui->labelPrivateSendEnabled->setToolTip(strKeysLeftText); - if (!fEnablePrivateSend) { - if (nBestHeight != darkSendPool.nCachedNumBlocks) { - darkSendPool.nCachedNumBlocks = nBestHeight; + if (!privateSendClient.fEnablePrivateSend) { + if (nBestHeight != privateSendClient.nCachedNumBlocks) { + privateSendClient.nCachedNumBlocks = nBestHeight; updatePrivateSendProgress(); } @@ -522,7 +522,7 @@ void OverviewPage::privateSendStatus() } } - QString strEnabled = fEnablePrivateSend ? tr("Enabled") : tr("Disabled"); + QString strEnabled = privateSendClient.fEnablePrivateSend ? tr("Enabled") : tr("Disabled"); // Show how many keys left in advanced PS UI mode only if(fShowAdvancedPSUI) strEnabled += ", " + strKeysLeftText; ui->labelPrivateSendEnabled->setText(strEnabled); @@ -544,13 +544,13 @@ void OverviewPage::privateSendStatus() } // check darksend status and unlock if needed - if(nBestHeight != darkSendPool.nCachedNumBlocks) { + if(nBestHeight != privateSendClient.nCachedNumBlocks) { // Balance and number of transactions might have changed - darkSendPool.nCachedNumBlocks = nBestHeight; + privateSendClient.nCachedNumBlocks = nBestHeight; updatePrivateSendProgress(); } - QString strStatus = QString(darkSendPool.GetStatus().c_str()); + QString strStatus = QString(privateSendClient.GetStatus().c_str()); QString s = tr("Last PrivateSend message:\n") + strStatus; @@ -559,21 +559,21 @@ void OverviewPage::privateSendStatus() ui->labelPrivateSendLastMessage->setText(s); - if(darkSendPool.nSessionDenom == 0){ + if(privateSendClient.nSessionDenom == 0){ ui->labelSubmittedDenom->setText(tr("N/A")); } else { - QString strDenom(darkSendPool.GetDenominationsToString(darkSendPool.nSessionDenom).c_str()); + QString strDenom(privateSendClient.GetDenominationsToString(privateSendClient.nSessionDenom).c_str()); ui->labelSubmittedDenom->setText(strDenom); } } void OverviewPage::privateSendAuto(){ - darkSendPool.DoAutomaticDenominating(); + privateSendClient.DoAutomaticDenominating(); } void OverviewPage::privateSendReset(){ - darkSendPool.ResetPool(); + privateSendClient.ResetPool(); QMessageBox::warning(this, tr("PrivateSend"), tr("PrivateSend was successfully reset."), @@ -595,7 +595,7 @@ void OverviewPage::togglePrivateSend(){ QMessageBox::Ok, QMessageBox::Ok); settings.setValue("hasMixed", "hasMixed"); } - if(!fEnablePrivateSend){ + if(!privateSendClient.fEnablePrivateSend){ CAmount nMinAmount = vecPrivateSendDenominations.back() + PRIVATESEND_COLLATERAL*4; if(currentBalance < nMinAmount){ QString strMinAmount(BitcoinUnits::formatWithUnit(nDisplayUnit, nMinAmount)); @@ -612,7 +612,7 @@ void OverviewPage::togglePrivateSend(){ if(!ctx.isValid()) { //unlock was cancelled - darkSendPool.nCachedNumBlocks = std::numeric_limits::max(); + privateSendClient.nCachedNumBlocks = std::numeric_limits::max(); QMessageBox::warning(this, tr("PrivateSend"), tr("Wallet is locked and user declined to unlock. Disabling PrivateSend."), QMessageBox::Ok, QMessageBox::Ok); @@ -623,18 +623,18 @@ void OverviewPage::togglePrivateSend(){ } - fEnablePrivateSend = !fEnablePrivateSend; - darkSendPool.nCachedNumBlocks = std::numeric_limits::max(); + privateSendClient.fEnablePrivateSend = !privateSendClient.fEnablePrivateSend; + privateSendClient.nCachedNumBlocks = std::numeric_limits::max(); - if(!fEnablePrivateSend){ + if(!privateSendClient.fEnablePrivateSend){ ui->togglePrivateSend->setText(tr("Start Mixing")); - darkSendPool.UnlockCoins(); + privateSendClient.UnlockCoins(); } else { ui->togglePrivateSend->setText(tr("Stop Mixing")); /* show darksend configuration if client has defaults set */ - if(nPrivateSendAmount == 0){ + if(privateSendClient.nPrivateSendAmount == 0){ DarksendConfig dlg(this); dlg.setModel(walletModel); dlg.exec(); @@ -669,5 +669,5 @@ void OverviewPage::DisablePrivateSendCompletely() { if (nWalletBackups <= 0) { ui->labelPrivateSendEnabled->setText("(" + tr("Disabled") + ")"); } - fEnablePrivateSend = false; + privateSendClient.fEnablePrivateSend = false; } diff --git a/src/qt/sendcoinsdialog.cpp b/src/qt/sendcoinsdialog.cpp index fda32520bb3f9..07ed60031f84c 100644 --- a/src/qt/sendcoinsdialog.cpp +++ b/src/qt/sendcoinsdialog.cpp @@ -23,7 +23,7 @@ #include "txmempool.h" #include "wallet/wallet.h" -#include "darksend.h" +#include "privatesend-client.h" #include #include diff --git a/src/qt/transactionrecord.cpp b/src/qt/transactionrecord.cpp index 5b5c11891940b..2b7515e529543 100644 --- a/src/qt/transactionrecord.cpp +++ b/src/qt/transactionrecord.cpp @@ -11,8 +11,8 @@ #include "timedata.h" #include "wallet/wallet.h" -#include "darksend.h" #include "instantx.h" +#include "privatesend.h" #include diff --git a/src/qt/walletmodel.cpp b/src/qt/walletmodel.cpp index d7fc3601e0c55..d8f5836ded0df 100644 --- a/src/qt/walletmodel.cpp +++ b/src/qt/walletmodel.cpp @@ -20,9 +20,9 @@ #include "wallet/wallet.h" #include "wallet/walletdb.h" // for BackupWallet -#include "darksend.h" #include "instantx.h" #include "spork.h" +#include "privatesend-client.h" #include @@ -141,13 +141,13 @@ void WalletModel::pollBalanceChanged() if(!lockWallet) return; - if(fForceCheckBalanceChanged || chainActive.Height() != cachedNumBlocks || nPrivateSendRounds != cachedPrivateSendRounds || cachedTxLocks != nCompleteTXLocks) + if(fForceCheckBalanceChanged || chainActive.Height() != cachedNumBlocks || privateSendClient.nPrivateSendRounds != cachedPrivateSendRounds || cachedTxLocks != nCompleteTXLocks) { fForceCheckBalanceChanged = false; // Balance and number of transactions might have changed cachedNumBlocks = chainActive.Height(); - cachedPrivateSendRounds = nPrivateSendRounds; + cachedPrivateSendRounds = privateSendClient.nPrivateSendRounds; checkBalanceChanged(); if(transactionTableModel) diff --git a/src/rpcmasternode.cpp b/src/rpcmasternode.cpp index 4f10fd540ffeb..8d46bd9fe6314 100644 --- a/src/rpcmasternode.cpp +++ b/src/rpcmasternode.cpp @@ -3,13 +3,14 @@ // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include "activemasternode.h" -#include "darksend.h" #include "init.h" #include "main.h" #include "masternode-payments.h" #include "masternode-sync.h" #include "masternodeconfig.h" #include "masternodeman.h" +#include "privatesend-client.h" +#include "privatesend-server.h" #include "rpcserver.h" #include "util.h" #include "utilmoneystr.h" @@ -42,18 +43,18 @@ UniValue privatesend(const UniValue& params, bool fHelp) if(fMasterNode) return "Mixing is not supported from masternodes"; - fEnablePrivateSend = true; - bool result = darkSendPool.DoAutomaticDenominating(); - return "Mixing " + (result ? "started successfully" : ("start failed: " + darkSendPool.GetStatus() + ", will retry")); + privateSendClient.fEnablePrivateSend = true; + bool result = privateSendClient.DoAutomaticDenominating(); + return "Mixing " + (result ? "started successfully" : ("start failed: " + privateSendClient.GetStatus() + ", will retry")); } if(params[0].get_str() == "stop") { - fEnablePrivateSend = false; + privateSendClient.fEnablePrivateSend = false; return "Mixing was stopped"; } if(params[0].get_str() == "reset") { - darkSendPool.ResetPool(); + privateSendClient.ResetPool(); return "Mixing was reset"; } @@ -67,16 +68,18 @@ UniValue getpoolinfo(const UniValue& params, bool fHelp) "getpoolinfo\n" "Returns an object containing mixing pool related information.\n"); + CPrivateSend privateSend = fMasterNode ? (CPrivateSend)privateSendServer : (CPrivateSend)privateSendClient; + UniValue obj(UniValue::VOBJ); - obj.push_back(Pair("state", darkSendPool.GetStateString())); - obj.push_back(Pair("mixing_mode", fPrivateSendMultiSession ? "multi-session" : "normal")); - obj.push_back(Pair("queue", darkSendPool.GetQueueSize())); - obj.push_back(Pair("entries", darkSendPool.GetEntriesCount())); - obj.push_back(Pair("status", darkSendPool.GetStatus())); - - if (darkSendPool.pSubmittedToMasternode) { - obj.push_back(Pair("outpoint", darkSendPool.pSubmittedToMasternode->vin.prevout.ToStringShort())); - obj.push_back(Pair("addr", darkSendPool.pSubmittedToMasternode->addr.ToString())); + obj.push_back(Pair("state", privateSend.GetStateString())); + obj.push_back(Pair("mixing_mode", (!fMasterNode && privateSendClient.fPrivateSendMultiSession) ? "multi-session" : "normal")); + obj.push_back(Pair("queue", privateSend.GetQueueSize())); + obj.push_back(Pair("entries", privateSend.GetEntriesCount())); + obj.push_back(Pair("status", privateSendClient.GetStatus())); + + if (privateSendClient.infoMixingMasternode.fInfoValid) { + obj.push_back(Pair("outpoint", privateSendClient.infoMixingMasternode.vin.prevout.ToStringShort())); + obj.push_back(Pair("addr", privateSendClient.infoMixingMasternode.addr.ToString())); } if (pwalletMain) { diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 537ba00d3fbde..c91f7a167b256 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -26,10 +26,10 @@ #include "util.h" #include "utilmoneystr.h" -#include "darksend.h" #include "governance.h" #include "instantx.h" #include "keepass.h" +#include "privatesend-client.h" #include "spork.h" #include @@ -1127,7 +1127,7 @@ int CWallet::GetInputPrivateSendRounds(CTxIn txin) const { LOCK(cs_wallet); int realPrivateSendRounds = GetRealInputPrivateSendRounds(txin, 0); - return realPrivateSendRounds > nPrivateSendRounds ? nPrivateSendRounds : realPrivateSendRounds; + return realPrivateSendRounds > privateSendClient.nPrivateSendRounds ? privateSendClient.nPrivateSendRounds : realPrivateSendRounds; } bool CWallet::IsDenominated(const CTxIn &txin) const @@ -1667,7 +1667,7 @@ CAmount CWalletTx::GetAnonymizedCredit(bool fUseCache) const if(pwallet->IsSpent(hashTx, i) || !pwallet->IsDenominated(txin)) continue; const int nRounds = pwallet->GetInputPrivateSendRounds(txin); - if(nRounds >= nPrivateSendRounds){ + if(nRounds >= privateSendClient.nPrivateSendRounds){ nCredit += pwallet->GetCredit(txout, ISMINE_SPENDABLE); if (!MoneyRange(nCredit)) throw std::runtime_error("CWalletTx::GetAnonymizedCredit() : value out of range"); @@ -1953,7 +1953,7 @@ CAmount CWallet::GetNormalizedAnonymizedBalance() const if (pcoin->GetDepthInMainChain() < 0) continue; int nRounds = GetInputPrivateSendRounds(txin); - nTotal += pcoin->vout[i].nValue * nRounds / nPrivateSendRounds; + nTotal += pcoin->vout[i].nValue * nRounds / privateSendClient.nPrivateSendRounds; } } } @@ -1966,7 +1966,7 @@ CAmount CWallet::GetNeedsToBeAnonymizedBalance(CAmount nMinBalance) const if(fLiteMode) return 0; CAmount nAnonymizedBalance = GetAnonymizedBalance(); - CAmount nNeedsToAnonymizeBalance = nPrivateSendAmount*COIN - nAnonymizedBalance; + CAmount nNeedsToAnonymizeBalance = privateSendClient.nPrivateSendAmount*COIN - nAnonymizedBalance; // try to overshoot target DS balance up to nMinBalance nNeedsToAnonymizeBalance += nMinBalance; @@ -1980,7 +1980,7 @@ CAmount CWallet::GetNeedsToBeAnonymizedBalance(CAmount nMinBalance) const if(nNeedsToAnonymizeBalance > nAnonymizableBalance) nNeedsToAnonymizeBalance = nAnonymizableBalance; // we should never exceed the pool max - if (nNeedsToAnonymizeBalance > darkSendPool.GetMaxPoolAmount()) nNeedsToAnonymizeBalance = darkSendPool.GetMaxPoolAmount(); + if (nNeedsToAnonymizeBalance > privateSendClient.GetMaxPoolAmount()) nNeedsToAnonymizeBalance = privateSendClient.GetMaxPoolAmount(); return nNeedsToAnonymizeBalance; } @@ -2353,7 +2353,7 @@ bool CWallet::SelectCoins(const CAmount& nTargetValue, setGetHash(),out.i); int nRounds = GetInputPrivateSendRounds(txin); // make sure it's actually anonymized - if(nRounds < nPrivateSendRounds) continue; + if(nRounds < privateSendClient.nPrivateSendRounds) continue; } nValueRet += out.tx->vout[out.i].nValue; setCoinsRet.insert(make_pair(out.tx, out.i)); @@ -2375,7 +2375,7 @@ bool CWallet::SelectCoins(const CAmount& nTargetValue, setGetHash(),out.i); int nRounds = GetInputPrivateSendRounds(txin); // make sure it's actually anonymized - if(nRounds < nPrivateSendRounds) continue; + if(nRounds < privateSendClient.nPrivateSendRounds) continue; nValueRet += nDenom; setCoinsRet.insert(make_pair(out.tx, out.i)); } @@ -2499,7 +2499,7 @@ bool CWallet::SelectCoinsByDenominations(int nDenom, CAmount nValueMin, CAmount // bit 3 - .1DASH+1 std::vector vecBits; - if (!darkSendPool.GetDenominationsBits(nDenom, vecBits)) { + if (!privateSendClient.GetDenominationsBits(nDenom, vecBits)) { return false; } @@ -2596,7 +2596,7 @@ bool CWallet::SelectCoinsGrouppedByAddresses(std::vector& vecT // otherwise they will just lead to higher fee / lower priority if(wtx.vout[i].nValue <= vecPrivateSendDenominations.back()/10) continue; // ignore anonymized - if(GetInputPrivateSendRounds(CTxIn(wtx.GetHash(), i)) >= nPrivateSendRounds) continue; + if(GetInputPrivateSendRounds(CTxIn(wtx.GetHash(), i)) >= privateSendClient.nPrivateSendRounds) continue; } CompactTallyItem& item = mapTally[address]; @@ -3045,7 +3045,7 @@ bool CWallet::CreateTransaction(const vector& vecSend, CWalletTx& wt nFeeRet += nChange; wtxNew.mapValue["DS"] = "1"; // recheck skipped denominations during next mixing - darkSendPool.ClearSkippedDenominations(); + privateSendClient.ClearSkippedDenominations(); } else { // Fill a vout to ourself @@ -3446,7 +3446,7 @@ bool CWallet::NewKeyPool() BOOST_FOREACH(int64_t nIndex, setKeyPool) walletdb.ErasePool(nIndex); setKeyPool.clear(); - fEnablePrivateSend = false; + privateSendClient.fEnablePrivateSend = false; nKeysLeftSinceAutoBackup = 0; if (IsLocked(true))