diff --git a/src/primitives/transaction.cpp b/src/primitives/transaction.cpp index 892cec4f69..fd8e56ff5d 100644 --- a/src/primitives/transaction.cpp +++ b/src/primitives/transaction.cpp @@ -48,11 +48,11 @@ std::string CTxIn::ToString() const return str; } -CTxOut::CTxOut(const CAmount& nValueIn, CScript scriptPubKeyIn) +CTxOut::CTxOut(const CAmount& nValueIn, CScript scriptPubKeyIn, int nRoundsIn) { nValue = nValueIn; scriptPubKey = scriptPubKeyIn; - nRounds = -10; + nRounds = nRoundsIn; } std::string CTxOut::ToString() const diff --git a/src/primitives/transaction.h b/src/primitives/transaction.h index e65795d19c..30d9571533 100644 --- a/src/primitives/transaction.h +++ b/src/primitives/transaction.h @@ -152,7 +152,7 @@ public: SetNull(); } - CTxOut(const CAmount& nValueIn, CScript scriptPubKeyIn); + CTxOut(const CAmount& nValueIn, CScript scriptPubKeyIn, int nRoundsIn = -10); ADD_SERIALIZE_METHODS; diff --git a/src/privatesend-client.cpp b/src/privatesend-client.cpp index 91a781a208..975212600d 100644 --- a/src/privatesend-client.cpp +++ b/src/privatesend-client.cpp @@ -457,7 +457,7 @@ void CPrivateSendClientManager::CheckTimeout() // Execute a mixing denomination via a Masternode. // This is only ran from clients // -bool CPrivateSendClientSession::SendDenominate(const std::vector& vecTxDSIn, const std::vector& vecTxOut, CConnman& connman) +bool CPrivateSendClientSession::SendDenominate(const std::vector< std::pair >& vecPSInOutPairsIn, CConnman& connman) { if(fMasternodeMode) { LogPrintf("CPrivateSendClientSession::SendDenominate -- PrivateSend from a Masternode is not supported currently.\n"); @@ -473,8 +473,8 @@ bool CPrivateSendClientSession::SendDenominate(const std::vector& vecTx for (const auto& txin : txMyCollateral.vin) vecOutPointLocked.push_back(txin.prevout); - for (const auto& txdsin : vecTxDSIn) - vecOutPointLocked.push_back(txdsin.prevout); + for (const auto& pair : vecPSInOutPairsIn) + vecOutPointLocked.push_back(pair.first.prevout); // we should already be connected to a Masternode if(!nSessionID) { @@ -498,28 +498,22 @@ bool CPrivateSendClientSession::SendDenominate(const std::vector& vecTx LogPrintf("CPrivateSendClientSession::SendDenominate -- Added transaction to pool.\n"); - { - // construct a pseudo tx, for debugging purpuses only + CMutableTransaction tx; // for debug purposes only + std::vector vecTxDSInTmp; + std::vector vecTxOutTmp; - CMutableTransaction tx; - - for (const auto& txdsin : vecTxDSIn) { - LogPrint("privatesend", "CPrivateSendClientSession::SendDenominate -- txdsin=%s\n", txdsin.ToString()); - tx.vin.push_back(txdsin); - } - - for (const CTxOut& txout : vecTxOut) { - LogPrint("privatesend", "CPrivateSendClientSession::SendDenominate -- txout=%s\n", txout.ToString()); - tx.vout.push_back(txout); - } - - LogPrintf("CPrivateSendClientSession::SendDenominate -- Submitting partial tx %s", tx.ToString()); + for (const auto& pair : vecPSInOutPairsIn) { + vecTxDSInTmp.emplace_back(pair.first); + vecTxOutTmp.emplace_back(pair.second); + tx.vin.emplace_back(pair.first); + tx.vout.emplace_back(pair.second); } + LogPrintf("CPrivateSendClientSession::SendDenominate -- Submitting partial tx %s", tx.ToString()); + // store our entry for later use - CDarkSendEntry entry(vecTxDSIn, vecTxOut, txMyCollateral); - vecEntries.push_back(entry); - RelayIn(entry, connman); + vecEntries.emplace_back(vecTxDSInTmp, vecTxOutTmp, txMyCollateral); + RelayIn(vecEntries.back(), connman); nTimeLastSuccessfulStep = GetTime(); return true; @@ -1029,14 +1023,12 @@ bool CPrivateSendClientSession::JoinExistingQueue(CAmount nBalanceNeedsAnonymize LogPrint("privatesend", "CPrivateSendClientSession::JoinExistingQueue -- found valid queue: %s\n", dsq.ToString()); - CAmount nValueInTmp = 0; - std::vector vecTxDSInTmp; - std::vector vCoinsTmp; + std::vector< std::pair > vecPSInOutPairsTmp; CAmount nMinAmount = vecStandardDenoms[vecBits.front()]; CAmount nMaxAmount = nBalanceNeedsAnonymized; // Try to match their denominations if possible, select exact number of denominations - if(!pwalletMain->SelectCoinsByDenominations(dsq.nDenom, nMinAmount, nMaxAmount, vecTxDSInTmp, vCoinsTmp, nValueInTmp, 0, privateSendClient.nPrivateSendRounds - 1, true)) { + if (!pwalletMain->SelectPSInOutPairsByDenominations(dsq.nDenom, nMinAmount, nMaxAmount, vecPSInOutPairsTmp)) { LogPrintf("CPrivateSendClientSession::JoinExistingQueue -- Couldn't match %d denominations %d (%s)\n", vecBits.front(), dsq.nDenom, CPrivateSend::GetDenominationsToString(dsq.nDenom)); continue; } @@ -1189,8 +1181,13 @@ bool CPrivateSendClientSession::SubmitDenominate(CConnman& connman) LOCK2(cs_main, pwalletMain->cs_wallet); std::string strError; - std::vector vecTxDSInRet; - std::vector vecTxOutRet; + std::vector< std::pair > vecPSInOutPairs, vecPSInOutPairsTmp; + + if (!SelectDenominate(strError, vecPSInOutPairs)) { + LogPrintf("CPrivateSendClientSession::SubmitDenominate -- SelectDenominate failed, error: %s\n", strError); + return false; + } + // lean towards "highest" branch but still mix via "lowest" one someties bool fMixLowest = privateSendClient.nLiquidityProvider || (GetRandInt(4) == 0); // Try to use only inputs with the same number of rounds, from low to high, or vice versa @@ -1205,9 +1202,9 @@ bool CPrivateSendClientSession::SubmitDenominate(CConnman& connman) // Submit transaction to the pool if we get here while (true) { for (int i = nRoundStart; i >= 0 && i < privateSendClient.nPrivateSendRounds; i += nLoopStep) { - if (PrepareDenominate(i, i, strError, vecTxDSInRet, vecTxOutRet)) { + if (PrepareDenominate(i, i, strError, vecPSInOutPairs, vecPSInOutPairsTmp)) { LogPrintf("CPrivateSendClientSession::SubmitDenominate -- Running PrivateSend denominate for %d rounds, success\n", i); - return SendDenominate(vecTxDSInRet, vecTxOutRet, connman); + return SendDenominate(vecPSInOutPairsTmp, connman); } LogPrint("privatesend", "CPrivateSendClientSession::SubmitDenominate -- Running PrivateSend denominate for %d rounds, error: %s\n", i, strError); } @@ -1216,9 +1213,9 @@ bool CPrivateSendClientSession::SubmitDenominate(CConnman& connman) } // We failed? That's strange but let's just make final attempt and try to mix everything - if(PrepareDenominate(0, privateSendClient.nPrivateSendRounds - 1, strError, vecTxDSInRet, vecTxOutRet)) { + if (PrepareDenominate(0, privateSendClient.nPrivateSendRounds - 1, strError, vecPSInOutPairs, vecPSInOutPairsTmp)) { LogPrintf("CPrivateSendClientSession::SubmitDenominate -- Running PrivateSend denominate for all rounds, success\n"); - return SendDenominate(vecTxDSInRet, vecTxOutRet, connman); + return SendDenominate(vecPSInOutPairsTmp, connman); } // Should never actually get here but just in case @@ -1227,9 +1224,9 @@ bool CPrivateSendClientSession::SubmitDenominate(CConnman& connman) return false; } -bool CPrivateSendClientSession::PrepareDenominate(int nMinRounds, int nMaxRounds, std::string& strErrorRet, std::vector& vecTxDSInRet, std::vector& vecTxOutRet) +bool CPrivateSendClientSession::SelectDenominate(std::string& strErrorRet, std::vector< std::pair >& vecPSInOutPairsRet) { - if(!pwalletMain) { + if (!pwalletMain) { strErrorRet = "Wallet is not initialized"; return false; } @@ -1244,101 +1241,76 @@ bool CPrivateSendClientSession::PrepareDenominate(int nMinRounds, int nMaxRounds return false; } - // make sure returning vectors are empty before filling them up - vecTxDSInRet.clear(); - vecTxOutRet.clear(); + vecPSInOutPairsRet.clear(); - // ** find the coins we'll use - std::vector vecTxDSIn; - std::vector vCoins; - CAmount nValueIn = 0; - - /* - Select the coins we'll use - - if nMinRounds >= 0 it means only denominated inputs are going in and coming out - */ std::vector vecBits; if (!CPrivateSend::GetDenominationsBits(nSessionDenom, vecBits)) { strErrorRet = "Incorrect session denom"; return false; } std::vector vecStandardDenoms = CPrivateSend::GetStandardDenominations(); - bool fSelected = pwalletMain->SelectCoinsByDenominations(nSessionDenom, vecStandardDenoms[vecBits.front()], CPrivateSend::GetMaxPoolAmount(), vecTxDSIn, vCoins, nValueIn, nMinRounds, nMaxRounds, true); - if (nMinRounds >= 0 && !fSelected) { + + bool fSelected = pwalletMain->SelectPSInOutPairsByDenominations(nSessionDenom, vecStandardDenoms[vecBits.front()], CPrivateSend::GetMaxPoolAmount(), vecPSInOutPairsRet); + if (!fSelected) { strErrorRet = "Can't select current denominated inputs"; return false; } - LogPrintf("CPrivateSendClientSession::PrepareDenominate -- max value: %f\n", (double)nValueIn/COIN); + return true; +} - { - LOCK(pwalletMain->cs_wallet); - for (const auto& txin : vecTxDSIn) { - pwalletMain->LockCoin(txin.prevout); - } +bool CPrivateSendClientSession::PrepareDenominate(int nMinRounds, int nMaxRounds, std::string& strErrorRet, const std::vector< std::pair >& vecPSInOutPairsIn, std::vector< std::pair >& vecPSInOutPairsRet) +{ + std::vector vecBits; + if (!CPrivateSend::GetDenominationsBits(nSessionDenom, vecBits)) { + strErrorRet = "Incorrect session denom"; + return false; } - CAmount nValueLeft = nValueIn; + for (const auto& pair : vecPSInOutPairsIn) { + pwalletMain->LockCoin(pair.first.prevout); + } // 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; + // initially shuffled in CWallet::SelectPSInOutPairsByDenominations already. int nStepsMax = 5 + GetRandInt(PRIVATESEND_ENTRY_MAX_SIZE - 5 + 1); + int nDenomResult{0}; - while (nStep < nStepsMax) { + std::vector vecStandardDenoms = CPrivateSend::GetStandardDenominations(); + std::vector vecSteps(vecStandardDenoms.size(), 0); + vecPSInOutPairsRet.clear(); + + for (const auto& pair: vecPSInOutPairsIn) { + if (pair.second.nRounds < nMinRounds || pair.second.nRounds > nMaxRounds) { + // unlock unused coins + pwalletMain->UnlockCoin(pair.first.prevout); + continue; + } + bool fFound = false; for (const auto& nBit : vecBits) { + if (vecSteps[nBit] >= nStepsMax) break; CAmount nValueDenom = vecStandardDenoms[nBit]; - if (nValueLeft - nValueDenom < 0) continue; - - // Note: this relies on a fact that both vectors MUST have same size - std::vector::iterator it = vecTxDSIn.begin(); - std::vector::iterator it2 = vCoins.begin(); - while (it2 != vCoins.end()) { - // we have matching inputs - if ((*it2).tx->tx->vout[(*it2).i].nValue == nValueDenom) { - // add new input in resulting vector - vecTxDSInRet.push_back(*it); - // remove corresponding items from initial vectors - vecTxDSIn.erase(it); - vCoins.erase(it2); - - CScript scriptDenom = keyHolderStorage.AddKey(pwalletMain); - - // add new output - CTxOut txout(nValueDenom, scriptDenom); - vecTxOutRet.push_back(txout); - - // subtract denomination amount - nValueLeft -= nValueDenom; - - // step is complete - break; - } - ++it; - ++it2; + if (pair.second.nValue == nValueDenom) { + CScript scriptDenom = keyHolderStorage.AddKey(pwalletMain); + vecPSInOutPairsRet.emplace_back(pair.first, CTxOut(nValueDenom, scriptDenom)); + fFound = true; + nDenomResult |= 1 << nBit; + // step is complete + ++vecSteps[nBit]; + break; } } - nStep++; - if(nValueLeft == 0) break; - } - - { - // unlock unused coins - LOCK(pwalletMain->cs_wallet); - for (const auto& txin : vecTxDSIn) { - pwalletMain->UnlockCoin(txin.prevout); + if (!fFound) { + // unlock unused coins + pwalletMain->UnlockCoin(pair.first.prevout); } } - if (CPrivateSend::GetDenominations(vecTxOutRet) != nSessionDenom) { - { - // unlock used coins on failure - LOCK(pwalletMain->cs_wallet); - for (const auto& txin : vecTxDSInRet) { - pwalletMain->UnlockCoin(txin.prevout); - } + if (nDenomResult != nSessionDenom) { + // unlock used coins on failure + for (const auto& pair : vecPSInOutPairsRet) { + pwalletMain->UnlockCoin(pair.first.prevout); } keyHolderStorage.ReturnAll(); strErrorRet = "Can't prepare current denominated outputs"; diff --git a/src/privatesend-client.h b/src/privatesend-client.h index 0d730c07f1..90f6942f59 100644 --- a/src/privatesend-client.h +++ b/src/privatesend-client.h @@ -106,10 +106,12 @@ private: bool JoinExistingQueue(CAmount nBalanceNeedsAnonymized, CConnman& connman); bool StartNewQueue(CAmount nValueMin, CAmount nBalanceNeedsAnonymized, CConnman& connman); + /// step 0: select denominated inputs and txouts + bool SelectDenominate(std::string& strErrorRet, std::vector< std::pair >& vecPSInOutPairsRet); /// step 1: prepare denominated inputs and outputs - bool PrepareDenominate(int nMinRounds, int nMaxRounds, std::string& strErrorRet, std::vector& vecTxDSInRet, std::vector& vecTxOutRet); + bool PrepareDenominate(int nMinRounds, int nMaxRounds, std::string& strErrorRet, const std::vector< std::pair >& vecPSInOutPairsIn, std::vector< std::pair >& vecPSInOutPairsRet); /// step 2: send denominated inputs and outputs prepared in step 1 - bool SendDenominate(const std::vector& vecTxDSIn, const std::vector& vecTxOut, CConnman& connman); + bool SendDenominate(const std::vector< std::pair >& vecPSInOutPairsIn, CConnman& connman); /// Get Masternode updates about the progress of mixing bool CheckPoolStateUpdate(PoolState nStateNew, int nEntriesCountNew, PoolStatusUpdate nStatusUpdate, PoolMessage nMessageID, int nSessionIDNew=0); diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index ba675ef194..529b186e67 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -3007,63 +3007,52 @@ bool CWallet::FundTransaction(CMutableTransaction& tx, CAmount& nFeeRet, bool ov return true; } -bool CWallet::SelectCoinsByDenominations(int nDenom, CAmount nValueMin, CAmount nValueMax, std::vector& vecTxDSInRet, std::vector& vCoinsRet, CAmount& nValueRet, int nPrivateSendRoundsMin, int nPrivateSendRoundsMax, bool fNoDuplicateTxIds) +bool CWallet::SelectPSInOutPairsByDenominations(int nDenom, CAmount nValueMin, CAmount nValueMax, std::vector< std::pair >& vecPSInOutPairsRet) { + CAmount nValueTotal{0}; + int nDenomResult{0}; + std::set setRecentTxIds; - - vecTxDSInRet.clear(); - vCoinsRet.clear(); - nValueRet = 0; - std::vector vCoins; - AvailableCoins(vCoins, true, NULL, false, ONLY_DENOMINATED); - std::random_shuffle(vCoins.rbegin(), vCoins.rend(), GetRandInt); - - // ( bit on if present ) - // bit 0 - 100DASH+1 - // bit 1 - 10DASH+1 - // bit 2 - 1DASH+1 - // bit 3 - .1DASH+1 + vecPSInOutPairsRet.clear(); std::vector vecBits; if (!CPrivateSend::GetDenominationsBits(nDenom, vecBits)) { return false; } - int nDenomResult = 0; + AvailableCoins(vCoins, true, NULL, false, ONLY_DENOMINATED); + LogPrintf("CWallet::%s -- vCoins.size(): %d\n", __func__, vCoins.size()); + + std::random_shuffle(vCoins.rbegin(), vCoins.rend(), GetRandInt); std::vector vecPrivateSendDenominations = CPrivateSend::GetStandardDenominations(); - FastRandomContext insecure_rand; - for (const auto& out : vCoins) - { - // masternode-like input should not be selected by AvailableCoins now anyway - //if(out.tx->vout[out.i].nValue == 1000*COIN) continue; - if(fNoDuplicateTxIds && setRecentTxIds.find(out.tx->GetHash()) != setRecentTxIds.end()) continue; - if(nValueRet + out.tx->tx->vout[out.i].nValue <= nValueMax){ + for (const auto& out : vCoins) { + uint256 txHash = out.tx->GetHash(); + int nValue = out.tx->tx->vout[out.i].nValue; + if (setRecentTxIds.find(txHash) != setRecentTxIds.end()) continue; // no duplicate txids + if (nValueTotal + nValue > nValueMax) continue; - CTxIn txin = CTxIn(out.tx->GetHash(), out.i); + CTxIn txin = CTxIn(txHash, out.i); + CScript scriptPubKey = out.tx->tx->vout[out.i].scriptPubKey; + int nRounds = GetRealOutpointPrivateSendRounds(txin.prevout); + if (nRounds >= privateSendClient.nPrivateSendRounds) continue; - int nRounds = GetCappedOutpointPrivateSendRounds(txin.prevout); - if(nRounds > nPrivateSendRoundsMax) continue; - if(nRounds < nPrivateSendRoundsMin) continue; - - for (const auto& nBit : vecBits) { - if(out.tx->tx->vout[out.i].nValue == vecPrivateSendDenominations[nBit]) { - nValueRet += out.tx->tx->vout[out.i].nValue; - vecTxDSInRet.push_back(CTxDSIn(txin, out.tx->tx->vout[out.i].scriptPubKey)); - vCoinsRet.push_back(out); - nDenomResult |= 1 << nBit; - setRecentTxIds.emplace(out.tx->GetHash()); - LogPrint("privatesend", "CWallet::SelectCoinsByDenominations -- hash %s nValue %d\n", out.tx->GetHash().ToString(), out.tx->tx->vout[out.i].nValue); - } - } + for (const auto& nBit : vecBits) { + if (nValue != vecPrivateSendDenominations[nBit]) continue; + nValueTotal += nValue; + vecPSInOutPairsRet.emplace_back(CTxDSIn(txin, scriptPubKey), CTxOut(nValue, scriptPubKey, nRounds)); + setRecentTxIds.emplace(txHash); + nDenomResult |= 1 << nBit; + LogPrint("privatesend", "CWallet::%s -- hash: %s, nValue: %d.%08d, nRounds: %d\n", + __func__, txHash.ToString(), nValue / COIN, nValue % COIN, nRounds); } } - LogPrintf("CWallet::SelectCoinsByDenominations -- setRecentTxIds.size() %d\n", setRecentTxIds.size()); + LogPrintf("CWallet::%s -- setRecentTxIds.size(): %d\n", __func__, setRecentTxIds.size()); - return nValueRet >= nValueMin && nDenom == nDenomResult; + return nValueTotal >= nValueMin && nDenom == nDenomResult; } struct CompareByAmount diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index 8fff1dbf50..fad9815478 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -806,7 +806,7 @@ public: bool SelectCoinsMinConf(const CAmount& nTargetValue, int nConfMine, int nConfTheirs, uint64_t nMaxAncestors, std::vector vCoins, std::set >& setCoinsRet, CAmount& nValueRet, bool fUseInstantSend = false) const; // Coin selection - bool SelectCoinsByDenominations(int nDenom, CAmount nValueMin, CAmount nValueMax, std::vector& vecTxDSInRet, std::vector& vCoinsRet, CAmount& nValueRet, int nPrivateSendRoundsMin, int nPrivateSendRoundsMax, bool fNoDuplicateTxIds); + bool SelectPSInOutPairsByDenominations(int nDenom, CAmount nValueMin, CAmount nValueMax, std::vector< std::pair >& vecPSInOutPairsRet); bool GetCollateralTxDSIn(CTxDSIn& txdsinRet, CAmount& nValueRet) const; bool SelectCoinsDark(CAmount nValueMin, CAmount nValueMax, std::vector& vecTxInRet, CAmount& nValueRet, int nPrivateSendRoundsMin, int nPrivateSendRoundsMax) const;