Overhaul of coin selection for mixing (#1364)

* Overhaul of coin selection for mixing

DoAutomaticDenominating logic should be:
- check pre-conditions,
- check denominations and collaterals,
- try using existing queue,
- try creating new queue.

Currently coins are selected too early and conditions are not quite right.

This is partially due to the fact that we no longer merge old inputs
and thus we are no longer able to calculate thresholds correctly using
SelectCoinsDark. To do this in a proper way we should use balances i.e.
GetAnonymizableBalance etc. Another issue is that we should take fee into
account when we calculate such balancies and when we select coins we should
ask for a correct denom, not just the smallest one as a minimum value.

And finally there are two bugs.
SelectCoinsGrouppedByAddresses: shouldn't push items smaller than
the smallest denom into resulting vector.
SelectCoinsDark: should allow small inputs in where "small" is defined
by nValueMin, not by some arbitrary amount.

* apply fee assumption for non-denoms only

* fix

* remove const
This commit is contained in:
UdjinM6 2017-03-14 09:21:37 +03:00 committed by GitHub
parent d2871209f9
commit d63080100a
3 changed files with 67 additions and 86 deletions

View File

@ -1382,79 +1382,46 @@ bool CDarksendPool::DoAutomaticDenominating(bool fDryRun)
return false;
}
// ** find the coins we'll use
std::vector<CTxIn> vecTxIn;
CAmount nValueMin = CENT;
CAmount nValueIn = 0;
CAmount nValueMin = vecPrivateSendDenominations.back();
CAmount nOnlyDenominatedBalance;
CAmount nBalanceNeedsDenominated;
CAmount nLowestDenom = vecPrivateSendDenominations.back();
// if there are no confirmed DS collateral inputs yet
if(!pwalletMain->HasCollateralInputs()) {
// should have some additional amount for them
nLowestDenom += PRIVATESEND_COLLATERAL*4;
nValueMin += PRIVATESEND_COLLATERAL*4;
}
CAmount nBalanceNeedsAnonymized = pwalletMain->GetNeedsToBeAnonymizedBalance(nLowestDenom);
// including denoms but applying some restrictions
CAmount nBalanceNeedsAnonymized = pwalletMain->GetNeedsToBeAnonymizedBalance(nValueMin);
// anonymizable balance is way too small
if(nBalanceNeedsAnonymized < nLowestDenom) {
if(nBalanceNeedsAnonymized < nValueMin) {
LogPrintf("CDarksendPool::DoAutomaticDenominating -- Not enough funds to anonymize\n");
strAutoDenomResult = _("Not enough funds to anonymize.");
return false;
}
LogPrint("privatesend", "CDarksendPool::DoAutomaticDenominating -- nLowestDenom: %f, nBalanceNeedsAnonymized: %f\n", (float)nLowestDenom/COIN, (float)nBalanceNeedsAnonymized/COIN);
// excluding denoms
CAmount nBalanceAnonimizableNonDenom = pwalletMain->GetAnonymizableBalance(true);
// denoms
CAmount nBalanceDenominatedConf = pwalletMain->GetDenominatedBalance();
CAmount nBalanceDenominatedUnconf = pwalletMain->GetDenominatedBalance(true);
CAmount nBalanceDenominated = nBalanceDenominatedConf + nBalanceDenominatedUnconf;
// select coins that should be given to the pool
if(!pwalletMain->SelectCoinsDark(nValueMin, nBalanceNeedsAnonymized, vecTxIn, nValueIn, 0, nPrivateSendRounds))
{
if(pwalletMain->SelectCoinsDark(nValueMin, 9999999*COIN, vecTxIn, nValueIn, -2, 0))
{
nOnlyDenominatedBalance = pwalletMain->GetDenominatedBalance(true) + pwalletMain->GetDenominatedBalance() - pwalletMain->GetAnonymizedBalance();
nBalanceNeedsDenominated = nBalanceNeedsAnonymized - nOnlyDenominatedBalance;
if(nBalanceNeedsDenominated > nValueIn) nBalanceNeedsDenominated = nValueIn;
LogPrint("privatesend", "CDarksendPool::DoAutomaticDenominating -- `SelectCoinsDark` (%f - (%f + %f - %f = %f) ) = %f\n",
(float)nBalanceNeedsAnonymized/COIN,
(float)pwalletMain->GetDenominatedBalance(true)/COIN,
(float)pwalletMain->GetDenominatedBalance()/COIN,
(float)pwalletMain->GetAnonymizedBalance()/COIN,
(float)nOnlyDenominatedBalance/COIN,
(float)nBalanceNeedsDenominated/COIN);
if(nBalanceNeedsDenominated < nLowestDenom) { // most likely we are just waiting for denoms to confirm
LogPrintf("CDarksendPool::DoAutomaticDenominating -- No funds detected in need of denominating\n");
strAutoDenomResult = _("No funds detected in need of denominating.");
return false;
}
if(!fDryRun) return CreateDenominated();
return true;
} else {
LogPrintf("CDarksendPool::DoAutomaticDenominating -- Can't denominate (no compatible inputs left)\n");
strAutoDenomResult = _("Can't denominate: no compatible inputs left.");
return false;
}
}
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;
nOnlyDenominatedBalance = pwalletMain->GetDenominatedBalance(true) + pwalletMain->GetDenominatedBalance() - pwalletMain->GetAnonymizedBalance();
nBalanceNeedsDenominated = nBalanceNeedsAnonymized - nOnlyDenominatedBalance;
LogPrint("privatesend", "CDarksendPool::DoAutomaticDenominating -- 'nBalanceNeedsDenominated > 0' (%f - (%f + %f - %f = %f) ) = %f\n",
(float)nBalanceNeedsAnonymized/COIN,
(float)pwalletMain->GetDenominatedBalance(true)/COIN,
(float)pwalletMain->GetDenominatedBalance()/COIN,
(float)pwalletMain->GetAnonymizedBalance()/COIN,
(float)nOnlyDenominatedBalance/COIN,
(float)nBalanceNeedsDenominated/COIN);
//check if we have should create more denominated inputs
if(nBalanceNeedsDenominated > 0) return CreateDenominated();
// 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())
@ -1470,7 +1437,8 @@ bool CDarksendPool::DoAutomaticDenominating(bool fDryRun)
UnlockCoins();
SetNull();
if(!fPrivateSendMultiSession && pwalletMain->GetDenominatedBalance(true) > 0) { //get denominated unconfirmed inputs
// 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;
@ -1525,8 +1493,11 @@ bool CDarksendPool::DoAutomaticDenominating(bool fDryRun)
if(pmn->nProtocolVersion < MIN_PRIVATESEND_PEER_PROTO_VERSION) continue;
// incompatible denom
if(dsq.nDenom >= (1 << vecPrivateSendDenominations.size())) continue;
std::vector<int> 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,
@ -1534,11 +1505,13 @@ bool CDarksendPool::DoAutomaticDenominating(bool fDryRun)
LogPrint("privatesend", "CDarksendPool::DoAutomaticDenominating -- found valid queue: %s\n", dsq.ToString());
CAmount nValueInTmp = 0;
std::vector<CTxIn> vecTxInTmp;
std::vector<COutput> vCoinsTmp;
// Try to match their denominations if possible
if(!pwalletMain->SelectCoinsByDenominations(dsq.nDenom, nValueMin, nBalanceNeedsAnonymized, vecTxInTmp, vCoinsTmp, nValueIn, 0, nPrivateSendRounds)) {
LogPrintf("CDarksendPool::DoAutomaticDenominating -- Couldn't match denominations %d (%s)\n", dsq.nDenom, GetDenominationsToString(dsq.nDenom));
// 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;
}
@ -1587,6 +1560,16 @@ bool CDarksendPool::DoAutomaticDenominating(bool fDryRun)
int nTries = 0;
// ** find the coins we'll use
std::vector<CTxIn> 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);
@ -1709,7 +1692,12 @@ 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, vecPrivateSendDenominations.back(), PRIVATESEND_POOL_MAX, vecTxIn, vCoins, nValueIn, nMinRounds, nMaxRounds);
std::vector<int> vecBits;
if (!GetDenominationsBits(nSessionDenom, vecBits)) {
strErrorRet = "Incorrect session denom";
return false;
}
bool fSelected = pwalletMain->SelectCoinsByDenominations(nSessionDenom, vecPrivateSendDenominations[vecBits.front()], PRIVATESEND_POOL_MAX, vecTxIn, vCoins, nValueIn, nMinRounds, nMaxRounds);
if (nMinRounds >= 0 && !fSelected) {
strErrorRet = "Can't select current denominated inputs";
return false;
@ -1731,11 +1719,6 @@ bool CDarksendPool::PrepareDenominate(int nMinRounds, int nMaxRounds, std::strin
// initially shuffled in CWallet::SelectCoinsByDenominations already.
int nStep = 0;
int nStepsMax = 5 + GetRandInt(5);
std::vector<int> vecBits;
if (!GetDenominationsBits(nSessionDenom, vecBits)) {
strErrorRet = "Incorrect session denom";
return false;
}
while (nStep < nStepsMax) {
BOOST_FOREACH(int nBit, vecBits) {

View File

@ -1856,18 +1856,20 @@ CAmount CWallet::GetBalance() const
return nTotal;
}
CAmount CWallet::GetAnonymizableBalance() const
CAmount CWallet::GetAnonymizableBalance(bool fSkipDenominated) const
{
if(fLiteMode) return 0;
std::vector<CompactTallyItem> vecTally;
if(!SelectCoinsGrouppedByAddresses(vecTally, false)) return 0;
if(!SelectCoinsGrouppedByAddresses(vecTally, fSkipDenominated)) return 0;
CAmount nTotal = 0;
BOOST_FOREACH(CompactTallyItem& item, vecTally) {
// try to anonymize all denoms and anything greater than sum of 10 smallest denoms
if(IsDenominatedAmount(item.nAmount) || item.nAmount >= vecPrivateSendDenominations.back() * 10)
bool fIsDenominated = IsDenominatedAmount(item.nAmount);
if(fSkipDenominated && fIsDenominated) continue;
// assume that the fee to create denoms be PRIVATESEND_COLLATERAL at max
if(item.nAmount >= vecPrivateSendDenominations.back() + (fIsDenominated ? 0 : PRIVATESEND_COLLATERAL))
nTotal += item.nAmount;
}
@ -2559,12 +2561,12 @@ bool CWallet::SelectCoinsGrouppedByAddresses(std::vector<CompactTallyItem>& vecT
if(fSkipDenominated && fAnonymizableTallyCachedNonDenom) {
vecTallyRet = vecAnonymizableTallyCachedNonDenom;
LogPrint("selectcoins", "SelectCoinsGrouppedByAddresses - using cache for non-denom inputs\n");
return true;
return vecTallyRet.size() > 0;
}
if(!fSkipDenominated && fAnonymizableTallyCached) {
vecTallyRet = vecAnonymizableTallyCached;
LogPrint("selectcoins", "SelectCoinsGrouppedByAddresses - using cache for all inputs\n");
return true;
return vecTallyRet.size() > 0;
}
}
@ -2605,13 +2607,12 @@ bool CWallet::SelectCoinsGrouppedByAddresses(std::vector<CompactTallyItem>& vecT
}
}
// we found nothing
if(mapTally.size() == 0) return false;
// construct resulting vector
vecTallyRet.clear();
BOOST_FOREACH(const PAIRTYPE(CBitcoinAddress, CompactTallyItem)& item, mapTally)
BOOST_FOREACH(const PAIRTYPE(CBitcoinAddress, CompactTallyItem)& item, mapTally) {
if(fAnonymizable && item.second.nAmount < vecPrivateSendDenominations.back()) continue;
vecTallyRet.push_back(item.second);
}
// order by amounts per address, from smallest to largest
sort(vecTallyRet.rbegin(), vecTallyRet.rend(), CompareByAmount());
@ -2633,7 +2634,7 @@ bool CWallet::SelectCoinsGrouppedByAddresses(std::vector<CompactTallyItem>& vecT
strMessage += strprintf(" %s %f\n", item.address.ToString().c_str(), float(item.nAmount)/COIN);
LogPrint("selectcoins", "%s", strMessage);
return true;
return vecTallyRet.size() > 0;
}
bool CWallet::SelectCoinsDark(CAmount nValueMin, CAmount nValueMax, std::vector<CTxIn>& vecTxInRet, CAmount& nValueRet, int nPrivateSendRoundsMin, int nPrivateSendRoundsMax) const
@ -2651,8 +2652,8 @@ bool CWallet::SelectCoinsDark(CAmount nValueMin, CAmount nValueMax, std::vector<
BOOST_FOREACH(const COutput& out, vCoins)
{
//do not allow inputs less than 1 CENT
if(out.tx->vout[out.i].nValue < CENT) continue;
//do not allow inputs less than 1/10th of minimum value
if(out.tx->vout[out.i].nValue < nValueMin/10) continue;
//do not allow collaterals to be selected
if(IsCollateralAmount(out.tx->vout[out.i].nValue)) continue;
if(fMasterNode && out.tx->vout[out.i].nValue == 1000*COIN) continue; //masternode input
@ -2670,10 +2671,7 @@ bool CWallet::SelectCoinsDark(CAmount nValueMin, CAmount nValueMax, std::vector<
}
}
// if it's more than min, we're good to return
if(nValueRet >= nValueMin) return true;
return false;
return nValueRet >= nValueMin;
}
bool CWallet::GetCollateralTxIn(CTxIn& txinRet, CAmount& nValueRet) const

View File

@ -741,7 +741,7 @@ public:
CAmount GetUnconfirmedWatchOnlyBalance() const;
CAmount GetImmatureWatchOnlyBalance() const;
CAmount GetAnonymizableBalance() const;
CAmount GetAnonymizableBalance(bool fSkipDenominated = false) const;
CAmount GetAnonymizedBalance() const;
double GetAverageAnonymizedRounds() const;
CAmount GetNormalizedAnonymizedBalance() const;