Refactor PS code to deal with denoms in a more generic way (#1071)

This commit is contained in:
UdjinM6 2016-10-16 23:23:17 +04:00 committed by GitHub
parent 27c445a891
commit bdcc9ab47c
4 changed files with 117 additions and 121 deletions

View File

@ -14,6 +14,7 @@
#include "script/sign.h"
#include "txmempool.h"
#include "util.h"
#include "utilmoneystr.h"
#include <boost/lexical_cast.hpp>
@ -1686,7 +1687,7 @@ bool CDarksendPool::PrepareDenominate(int nMinRounds, int nMaxRounds, std::strin
if nMinRounds >= 0 it means only denominated inputs are going in and coming out
*/
bool fSelected = pwalletMain->SelectCoinsByDenominations(nSessionDenom, 0.1*COIN, PRIVATESEND_POOL_MAX, vecTxIn, vCoins, nValueIn, nMinRounds, nMaxRounds);
bool fSelected = pwalletMain->SelectCoinsByDenominations(nSessionDenom, vecPrivateSendDenominations.back(), PRIVATESEND_POOL_MAX, vecTxIn, vCoins, nValueIn, nMinRounds, nMaxRounds);
if (nMinRounds >= 0 && !fSelected) {
strErrorRet = "Can't select current denominated inputs";
return false;
@ -1703,65 +1704,57 @@ bool CDarksendPool::PrepareDenominate(int nMinRounds, int nMaxRounds, std::strin
CAmount nValueLeft = nValueIn;
/*
TODO: Front load with needed denominations (e.g. .1, 1 )
*/
// Make outputs by looping through denominations: try to add every needed denomination, repeat up to 5-10 times.
// This way we can be pretty sure that it should have at least one of each needed denomination.
// Try to add every needed denomination, repeat up to 5-9 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(5);
while(nStep < nStepsMax) {
std::vector<int> vecBits;
if (!GetDenominationsBits(nSessionDenom, vecBits)) {
strErrorRet = "Incorrect session denom";
return false;
}
BOOST_FOREACH(CAmount nValueDenom, vecPrivateSendDenominations) {
// only use the ones that are approved
if (!((nSessionDenom & (1 << 0)) && nValueDenom == 100*COIN +100000) &&
!((nSessionDenom & (1 << 1)) && nValueDenom == 10*COIN + 10000) &&
!((nSessionDenom & (1 << 2)) && nValueDenom == 1*COIN + 1000) &&
!((nSessionDenom & (1 << 3)) && nValueDenom == .1*COIN + 100))
{ continue; }
while (nStep < nStepsMax) {
BOOST_FOREACH(int nBit, vecBits) {
CAmount nValueDenom = vecPrivateSendDenominations[nBit];
if (nValueLeft - nValueDenom < 0) continue;
// try to add it
if (nValueLeft - nValueDenom >= 0) {
// Note: this relies on a fact that both vectors MUST have same size
std::vector<CTxIn>::iterator it = vecTxIn.begin();
std::vector<COutput>::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);
// Note: this relies on a fact that both vectors MUST have same size
std::vector<CTxIn>::iterator it = vecTxIn.begin();
std::vector<COutput>::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();
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);
// add new output
CTxOut txout(nValueDenom, scriptChange);
vecTxOutRet.push_back(txout);
// subtract denomination amount
nValueLeft -= nValueDenom;
// subtract denomination amount
nValueLeft -= nValueDenom;
break;
}
++it;
++it2;
// step is complete
break;
}
++it;
++it2;
}
}
nStep++;
if(nValueLeft == 0) break;
nStep++;
}
{
@ -2071,38 +2064,34 @@ bool CDarksendPool::IsDenomCompatibleWithSession(int nDenom, CTransaction txColl
}
/* Create a nice string to show the denominations
Function returns as follows:
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
bit 4 and so on - non-denom
*/
std::string CDarksendPool::GetDenominationsToString(int nDenom)
{
std::string strDenom;
std::string strDenom = "";
int nMaxDenoms = vecPrivateSendDenominations.size();
if(nDenom & (1 << 0)) strDenom += "100";
if(nDenom & (1 << 1)) {
if(strDenom.size() > 0) strDenom += "+";
strDenom += "10";
if(nDenom >= (1 << nMaxDenoms)) {
return "out-of-bounds";
}
if(nDenom & (1 << 2)) {
if(strDenom.size() > 0) strDenom += "+";
strDenom += "1";
for (int i = 0; i < nMaxDenoms; ++i) {
if(nDenom & (1 << i)) {
strDenom += (strDenom.empty() ? "" : "+") + FormatMoney(vecPrivateSendDenominations[i]);
}
}
if(nDenom & (1 << 3)) {
if(strDenom.size() > 0) strDenom += "+";
strDenom += "0.1";
if(strDenom.empty()) {
return "non-denom";
}
if(strDenom.size() == 0 && nDenom >= (1 << 4)) strDenom += "non-denom";
return strDenom;
}
@ -2117,7 +2106,7 @@ int CDarksendPool::GetDenominations(const std::vector<CTxDSOut>& vecTxDSOut)
}
/* Return a bitshifted integer representing the denominations in this list
Function returns as follows:
Function returns as follows (for 4 denominations):
( bit on if present )
100 - bit 0
10 - bit 1
@ -2157,6 +2146,29 @@ int CDarksendPool::GetDenominations(const std::vector<CTxOut>& vecTxOut, bool fS
return nDenom;
}
bool CDarksendPool::GetDenominationsBits(int nDenom, std::vector<int> &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<CAmount>& vecAmount)
{
CScript scriptTmp = CScript();

View File

@ -446,6 +446,7 @@ public:
int GetDenominations(const std::vector<CTxOut>& vecTxOut, bool fSingleRandomDenom = false);
int GetDenominations(const std::vector<CTxDSOut>& vecTxDSOut);
std::string GetDenominationsToString(int nDenom);
bool GetDenominationsBits(int nDenom, std::vector<int> &vecBitsRet);
void SetMinBlockSpacing(int nMinBlockSpacingIn) { nMinBlockSpacing = nMinBlockSpacingIn; }

View File

@ -2331,12 +2331,12 @@ bool CWallet::SelectCoinsMinConf(const CAmount& nTargetValue, int nConfMine, int
return true;
}
bool CWallet::SelectCoins(const CAmount& nTargetValue, set<pair<const CWalletTx*,unsigned int> >& setCoinsRet, CAmount& nValueRet, const CCoinControl* coinControl, AvailableCoinsType coin_type, bool fUseInstantSend) const
bool CWallet::SelectCoins(const CAmount& nTargetValue, set<pair<const CWalletTx*,unsigned int> >& setCoinsRet, CAmount& nValueRet, const CCoinControl* coinControl, AvailableCoinsType nCoinType, bool fUseInstantSend) const
{
// Note: this function should never be used for "always free" tx types like dstx
vector<COutput> vCoins;
AvailableCoins(vCoins, true, coinControl, false, coin_type, fUseInstantSend);
AvailableCoins(vCoins, true, coinControl, false, nCoinType, fUseInstantSend);
// coin control -> return all selected outputs (we want all selected to go into the transaction for sure)
if (coinControl && coinControl->HasSelected() && !coinControl->fAllowOtherInputs)
@ -2346,11 +2346,11 @@ bool CWallet::SelectCoins(const CAmount& nTargetValue, set<pair<const CWalletTx*
if(!out.fSpendable)
continue;
if(coin_type == ONLY_DENOMINATED) {
CTxIn vin = CTxIn(out.tx->GetHash(),out.i);
int rounds = GetInputPrivateSendRounds(vin);
if(nCoinType == ONLY_DENOMINATED) {
CTxIn txin = CTxIn(out.tx->GetHash(),out.i);
int nRounds = GetInputPrivateSendRounds(txin);
// make sure it's actually anonymized
if(rounds < nPrivateSendRounds) continue;
if(nRounds < nPrivateSendRounds) continue;
}
nValueRet += out.tx->vout[out.i].nValue;
setCoinsRet.insert(make_pair(out.tx, out.i));
@ -2358,21 +2358,21 @@ bool CWallet::SelectCoins(const CAmount& nTargetValue, set<pair<const CWalletTx*
return (nValueRet >= nTargetValue);
}
//if we're doing only denominated, we need to round up to the nearest .1DRK
if(coin_type == ONLY_DENOMINATED) {
//if we're doing only denominated, we need to round up to the nearest smallest denomination
if(nCoinType == ONLY_DENOMINATED) {
CAmount nSmallestDenom = vecPrivateSendDenominations.back();
// Make outputs by looping through denominations, from large to small
BOOST_FOREACH(CAmount v, vecPrivateSendDenominations)
BOOST_FOREACH(CAmount nDenom, vecPrivateSendDenominations)
{
BOOST_FOREACH(const COutput& out, vCoins)
{
if(out.tx->vout[out.i].nValue == v //make sure it's the denom we're looking for
&& nValueRet + out.tx->vout[out.i].nValue < nTargetValue + (0.1*COIN)+100 //round the amount up to .1DRK over
){
CTxIn vin = CTxIn(out.tx->GetHash(),out.i);
int rounds = GetInputPrivateSendRounds(vin);
//make sure it's the denom we're looking for, round the amount up to smallest denom
if(out.tx->vout[out.i].nValue == nDenom && nValueRet + nDenom < nTargetValue + nSmallestDenom) {
CTxIn txin = CTxIn(out.tx->GetHash(),out.i);
int nRounds = GetInputPrivateSendRounds(txin);
// make sure it's actually anonymized
if(rounds < nPrivateSendRounds) continue;
nValueRet += out.tx->vout[out.i].nValue;
if(nRounds < nPrivateSendRounds) continue;
nValueRet += nDenom;
setCoinsRet.insert(make_pair(out.tx, out.i));
}
}
@ -2480,19 +2480,13 @@ bool CWallet::FundTransaction(CMutableTransaction& tx, CAmount &nFeeRet, int& nC
bool CWallet::SelectCoinsByDenominations(int nDenom, CAmount nValueMin, CAmount nValueMax, std::vector<CTxIn>& vecTxInRet, std::vector<COutput>& vCoinsRet, CAmount& nValueRet, int nPrivateSendRoundsMin, int nPrivateSendRoundsMax)
{
vecTxInRet.clear();
vCoinsRet.clear();
nValueRet = 0;
vCoinsRet.clear();
vector<COutput> vCoins;
AvailableCoins(vCoins, true, NULL, false, ONLY_DENOMINATED);
std::random_shuffle(vCoins.rbegin(), vCoins.rend());
//keep track of each denomination that we have
bool fFound100 = false;
bool fFound10 = false;
bool fFound1 = false;
bool fFoundDot1 = false;
std::random_shuffle(vCoins.rbegin(), vCoins.rend(), GetRandInt);
// ( bit on if present )
// bit 0 - 100DASH+1
@ -2500,56 +2494,45 @@ bool CWallet::SelectCoinsByDenominations(int nDenom, CAmount nValueMin, CAmount
// bit 2 - 1DASH+1
// bit 3 - .1DASH+1
//Check to see if any of the denomination are off, in that case mark them as fulfilled
if(!(nDenom & (1 << 0))) fFound100 = true;
if(!(nDenom & (1 << 1))) fFound10 = true;
if(!(nDenom & (1 << 2))) fFound1 = true;
if(!(nDenom & (1 << 3))) fFoundDot1 = true;
std::vector<int> vecBits;
if (!darkSendPool.GetDenominationsBits(nDenom, vecBits)) {
return false;
}
int nDenomResult = 0;
BOOST_FOREACH(const COutput& out, vCoins)
{
// masternode-like input should not be selected by AvailableCoins now anyway
//if(out.tx->vout[out.i].nValue == 1000*COIN) continue;
if(nValueRet + out.tx->vout[out.i].nValue <= nValueMax){
bool fAccepted = false;
CTxIn txin = CTxIn(out.tx->GetHash(),out.i);
CTxIn txin = CTxIn(out.tx->GetHash(), out.i);
int nRounds = GetInputPrivateSendRounds(txin);
if(nRounds >= nPrivateSendRoundsMax) continue;
if(nRounds < nPrivateSendRoundsMin) continue;
if(fFound100 && fFound10 && fFound1 && fFoundDot1){ //if fulfilled
//we can return this for submission
if(nValueRet >= nValueMin){
//random reduce the max amount we'll submit for anonymity
nValueMax -= (rand() % (nValueMax/5));
//on average use 50% of the inputs or less
int r = (rand() % (int)vCoins.size());
if((int)vecTxInRet.size() > r) return true;
BOOST_FOREACH(int nBit, vecBits) {
if(out.tx->vout[out.i].nValue == vecPrivateSendDenominations[nBit]) {
if(nValueRet >= nValueMin) {
//randomly reduce the max amount we'll submit (for anonymity)
nValueMax -= (GetInsecureRand(nValueMax/5));
//on average use 50% of the inputs or less
int r = GetInsecureRand((int)vCoins.size());
if((int)vecTxInRet.size() > r) return true;
}
txin.prevPubKey = out.tx->vout[out.i].scriptPubKey; // the inputs PubKey
nValueRet += out.tx->vout[out.i].nValue;
vecTxInRet.push_back(txin);
vCoinsRet.push_back(out);
nDenomResult |= 1 << nBit;
}
//Denomination criterion has been met, we can take any matching denominations
if((nDenom & (1 << 0)) && out.tx->vout[out.i].nValue == ((100*COIN) +100000)) {fAccepted = true;}
else if((nDenom & (1 << 1)) && out.tx->vout[out.i].nValue == ((10*COIN)+10000)) {fAccepted = true;}
else if((nDenom & (1 << 2)) && out.tx->vout[out.i].nValue == ((1*COIN) +1000)) {fAccepted = true;}
else if((nDenom & (1 << 3)) && out.tx->vout[out.i].nValue == ((.1*COIN)+100)) {fAccepted = true;}
} else {
//Criterion has not been satisfied, we will only take 1 of each until it is.
if((nDenom & (1 << 0)) && out.tx->vout[out.i].nValue == ((100*COIN) +100000)) {fAccepted = true; fFound100 = true;}
else if((nDenom & (1 << 1)) && out.tx->vout[out.i].nValue == ((10*COIN)+10000)) {fAccepted = true; fFound10 = true;}
else if((nDenom & (1 << 2)) && out.tx->vout[out.i].nValue == ((1*COIN) +1000)) {fAccepted = true; fFound1 = true;}
else if((nDenom & (1 << 3)) && out.tx->vout[out.i].nValue == ((.1*COIN)+100)) {fAccepted = true; fFoundDot1 = true;}
}
if(!fAccepted) continue;
txin.prevPubKey = out.tx->vout[out.i].scriptPubKey; // the inputs PubKey
nValueRet += out.tx->vout[out.i].nValue;
vecTxInRet.push_back(txin);
vCoinsRet.push_back(out);
}
}
return (nValueRet >= nValueMin && fFound100 && fFound10 && fFound1 && fFoundDot1);
return nValueRet >= nValueMin && nDenom == nDenomResult;
}
struct CompareByAmount

View File

@ -518,7 +518,7 @@ private:
* all coins from coinControl are selected; Never select unconfirmed coins
* if they are not ours
*/
bool SelectCoins(const CAmount& nTargetValue, std::set<std::pair<const CWalletTx*,unsigned int> >& setCoinsRet, CAmount& nValueRet, const CCoinControl *coinControl = NULL, AvailableCoinsType coin_type=ALL_COINS, bool fUseInstantSend = true) const;
bool SelectCoins(const CAmount& nTargetValue, std::set<std::pair<const CWalletTx*,unsigned int> >& setCoinsRet, CAmount& nValueRet, const CCoinControl *coinControl = NULL, AvailableCoinsType nCoinType=ALL_COINS, bool fUseInstantSend = true) const;
CWalletDB *pwalletdbEncryption;