Skip to content

Commit

Permalink
Be more accurate with denom creation/consumption (#2853)
Browse files Browse the repository at this point in the history
* Be more accurate with denom creation/consumption

- Calculate and pass an actual balance needed to be denominated to CreateDenominated.
- Drop GetNeedsToBeAnonymizedBalance and fix corresponding conditions: do not overshoot, do not check max pool amount - these conditions are handled outside.
- Properly calculate various balance limits.
- Handle edge case for the final denom.

* Add an option to control max number of denoms created and respect it

`-privatesenddenoms`, default is 300

Note: CreateDenominated failure is not an error anymore

* Add few more stats to log in DoAutomaticDenominating
  • Loading branch information
UdjinM6 committed Apr 30, 2019
1 parent 3d993ee commit b3ed641
Show file tree
Hide file tree
Showing 5 changed files with 107 additions and 86 deletions.
5 changes: 5 additions & 0 deletions src/init.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -597,6 +597,7 @@ std::string HelpMessage(HelpMessageMode mode)
strUsage += HelpMessageOpt("-privatesendsessions=<n>", strprintf(_("Use N separate masternodes in parallel to mix funds (%u-%u, default: %u)"), MIN_PRIVATESEND_SESSIONS, MAX_PRIVATESEND_SESSIONS, DEFAULT_PRIVATESEND_SESSIONS));
strUsage += HelpMessageOpt("-privatesendrounds=<n>", strprintf(_("Use N separate masternodes for each denominated input to mix funds (%u-%u, default: %u)"), MIN_PRIVATESEND_ROUNDS, MAX_PRIVATESEND_ROUNDS, DEFAULT_PRIVATESEND_ROUNDS));
strUsage += HelpMessageOpt("-privatesendamount=<n>", strprintf(_("Keep N DASH anonymized (%u-%u, default: %u)"), MIN_PRIVATESEND_AMOUNT, MAX_PRIVATESEND_AMOUNT, DEFAULT_PRIVATESEND_AMOUNT));
strUsage += HelpMessageOpt("-privatesenddenoms=<n>", strprintf(_("Create up to N inputs of each denominated amount (%u-%u, default: %u)"), MIN_PRIVATESEND_DENOMS, MAX_PRIVATESEND_DENOMS, DEFAULT_PRIVATESEND_DENOMS));
strUsage += HelpMessageOpt("-liquidityprovider=<n>", strprintf(_("Provide liquidity to PrivateSend by infrequently mixing coins on a continual basis (%u-%u, default: %u, 1=very frequent, high fees, %u=very infrequent, low fees)"),
MIN_PRIVATESEND_LIQUIDITY, MAX_PRIVATESEND_LIQUIDITY, DEFAULT_PRIVATESEND_LIQUIDITY, MAX_PRIVATESEND_LIQUIDITY));
#endif // ENABLE_WALLET
Expand Down Expand Up @@ -961,6 +962,8 @@ void InitParameterInteraction()
LogPrintf("%s: parameter interaction: -liquidityprovider=%d -> setting -privatesendrounds=%d\n", __func__, nLiqProvTmp, itostr(std::numeric_limits<int>::max()));
ForceSetArg("-privatesendamount", itostr(MAX_PRIVATESEND_AMOUNT));
LogPrintf("%s: parameter interaction: -liquidityprovider=%d -> setting -privatesendamount=%d\n", __func__, nLiqProvTmp, MAX_PRIVATESEND_AMOUNT);
ForceSetArg("-privatesenddenoms", itostr(MAX_PRIVATESEND_DENOMS));
LogPrintf("%s: parameter interaction: -liquidityprovider=%d -> setting -privatesenddenoms=%d\n", __func__, nLiqProvTmp, itostr(MAX_PRIVATESEND_DENOMS));
ForceSetArg("-privatesendmultisession", "0");
LogPrintf("%s: parameter interaction: -liquidityprovider=%d -> setting -privatesendmultisession=0\n", __func__, nLiqProvTmp);
}
Expand Down Expand Up @@ -1973,10 +1976,12 @@ bool AppInitMain(boost::thread_group& threadGroup, CScheduler& scheduler)
privateSendClient.nPrivateSendSessions = std::min(std::max((int)GetArg("-privatesendsessions", DEFAULT_PRIVATESEND_SESSIONS), MIN_PRIVATESEND_SESSIONS), MAX_PRIVATESEND_SESSIONS);
privateSendClient.nPrivateSendRounds = std::min(std::max((int)GetArg("-privatesendrounds", DEFAULT_PRIVATESEND_ROUNDS), MIN_PRIVATESEND_ROUNDS), nMaxRounds);
privateSendClient.nPrivateSendAmount = std::min(std::max((int)GetArg("-privatesendamount", DEFAULT_PRIVATESEND_AMOUNT), MIN_PRIVATESEND_AMOUNT), MAX_PRIVATESEND_AMOUNT);
privateSendClient.nPrivateSendDenoms = std::min(std::max((int)GetArg("-privatesenddenoms", DEFAULT_PRIVATESEND_DENOMS), MIN_PRIVATESEND_DENOMS), MAX_PRIVATESEND_DENOMS);

LogPrintf("PrivateSend liquidityprovider: %d\n", privateSendClient.nLiquidityProvider);
LogPrintf("PrivateSend rounds: %d\n", privateSendClient.nPrivateSendRounds);
LogPrintf("PrivateSend amount: %d\n", privateSendClient.nPrivateSendAmount);
LogPrintf("PrivateSend denoms: %d\n", privateSendClient.nPrivateSendDenoms);
#endif // ENABLE_WALLET

CPrivateSend::InitStandardDenominations();
Expand Down
151 changes: 94 additions & 57 deletions src/privatesend-client.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -814,7 +814,6 @@ bool CPrivateSendClientSession::DoAutomaticDenominating(CConnman& connman, bool
}

CAmount nBalanceNeedsAnonymized;
CAmount nValueMin = CPrivateSend::GetSmallestDenomination();

{
LOCK2(cs_main, pwalletMain->cs_wallet);
Expand All @@ -841,17 +840,29 @@ bool CPrivateSendClientSession::DoAutomaticDenominating(CConnman& connman, bool
return false;
}

// check if there is anything left to do
CAmount nBalanceAnonymized = pwalletMain->GetAnonymizedBalance();
nBalanceNeedsAnonymized = privateSendClient.nPrivateSendAmount*COIN - nBalanceAnonymized;

if (nBalanceNeedsAnonymized < 0) {
LogPrint("privatesend", "CPrivateSendClientSession::DoAutomaticDenominating -- Nothing to do\n");
// nothing to do, just keep it in idle mode
return false;
}

CAmount nValueMin = CPrivateSend::GetSmallestDenomination();

// if there are no confirmed DS collateral inputs yet
if (!pwalletMain->HasCollateralInputs()) {
// should have some additional amount for them
nValueMin += CPrivateSend::GetMaxCollateralAmount();
}

// including denoms but applying some restrictions
nBalanceNeedsAnonymized = pwalletMain->GetNeedsToBeAnonymizedBalance(nValueMin);
CAmount nBalanceAnonymizable = pwalletMain->GetAnonymizableBalance();

// anonymizable balance is way too small
if (nBalanceNeedsAnonymized < nValueMin) {
if (nBalanceAnonymizable < nValueMin) {
LogPrintf("CPrivateSendClientSession::DoAutomaticDenominating -- Not enough funds to anonymize\n");
strAutoDenomResult = _("Not enough funds to anonymize.");
return false;
Expand All @@ -863,29 +874,51 @@ bool CPrivateSendClientSession::DoAutomaticDenominating(CConnman& connman, bool
CAmount nBalanceDenominatedConf = pwalletMain->GetDenominatedBalance();
CAmount nBalanceDenominatedUnconf = pwalletMain->GetDenominatedBalance(true);
CAmount nBalanceDenominated = nBalanceDenominatedConf + nBalanceDenominatedUnconf;
CAmount nBalanceToDenominate = privateSendClient.nPrivateSendAmount * COIN - nBalanceDenominated;

// adjust nBalanceNeedsAnonymized to consume final denom
if (nBalanceDenominated - nBalanceAnonymized > nBalanceNeedsAnonymized) {
auto denoms = CPrivateSend::GetStandardDenominations();
CAmount nAdditionalDenom{0};
for (const auto& denom : denoms) {
if (nBalanceNeedsAnonymized < denom) {
nAdditionalDenom = denom;
} else {
break;
}
}
nBalanceNeedsAnonymized += nAdditionalDenom;
}

LogPrint("privatesend", "CPrivateSendClientSession::DoAutomaticDenominating -- current stats:\n"
" nValueMin: %s\n"
" nBalanceAnonymizable: %s\n"
" nBalanceAnonymized: %s\n"
" nBalanceNeedsAnonymized: %s\n"
" nBalanceAnonimizableNonDenom: %s\n"
" nBalanceDenominatedConf: %s\n"
" nBalanceDenominatedUnconf: %s\n"
" nBalanceDenominated: %s\n",
" nBalanceToDenominate: %s\n",
FormatMoney(nValueMin),
FormatMoney(nBalanceAnonymizable),
FormatMoney(nBalanceAnonymized),
FormatMoney(nBalanceNeedsAnonymized),
FormatMoney(nBalanceAnonimizableNonDenom),
FormatMoney(nBalanceDenominatedConf),
FormatMoney(nBalanceDenominatedUnconf),
FormatMoney(nBalanceDenominated)
FormatMoney(nBalanceDenominated),
FormatMoney(nBalanceToDenominate)
);

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 + CPrivateSend::GetCollateralAmount() && nBalanceDenominated < privateSendClient.nPrivateSendAmount * COIN)
return CreateDenominated(connman);
if (nBalanceAnonimizableNonDenom >= nValueMin + CPrivateSend::GetCollateralAmount() && nBalanceToDenominate > 0) {
CreateDenominated(nBalanceToDenominate, connman);
}

//check if we have the collateral sized inputs
if (!pwalletMain->HasCollateralInputs())
Expand Down Expand Up @@ -935,7 +968,7 @@ bool CPrivateSendClientSession::DoAutomaticDenominating(CConnman& connman, bool
// do not initiate queue if we are a liquidity provider to avoid useless inter-mixing
if (privateSendClient.nLiquidityProvider) return false;

if (StartNewQueue(nValueMin, nBalanceNeedsAnonymized, connman))
if (StartNewQueue(nBalanceNeedsAnonymized, connman))
return true;

strAutoDenomResult = _("No compatible Masternode found.");
Expand Down Expand Up @@ -1107,17 +1140,18 @@ bool CPrivateSendClientSession::JoinExistingQueue(CAmount nBalanceNeedsAnonymize
return false;
}

bool CPrivateSendClientSession::StartNewQueue(CAmount nValueMin, CAmount nBalanceNeedsAnonymized, CConnman& connman)
bool CPrivateSendClientSession::StartNewQueue(CAmount nBalanceNeedsAnonymized, CConnman& connman)
{
if (!pwalletMain) return false;
if (nBalanceNeedsAnonymized <= 0) return false;

int nTries = 0;
int nMnCount = deterministicMNManager->GetListAtChainTip().GetValidMNsCount();

// ** find the coins we'll use
std::vector<CTxIn> vecTxIn;
CAmount nValueInTmp = 0;
if (!pwalletMain->SelectPrivateCoins(nValueMin, nBalanceNeedsAnonymized, vecTxIn, nValueInTmp, 0, privateSendClient.nPrivateSendRounds - 1)) {
if (!pwalletMain->SelectPrivateCoins(CPrivateSend::GetSmallestDenomination(), nBalanceNeedsAnonymized, vecTxIn, nValueInTmp, 0, privateSendClient.nPrivateSendRounds - 1)) {
// this should never happen
LogPrintf("CPrivateSendClientSession::StartNewQueue -- Can't mix: no compatible inputs found!\n");
strAutoDenomResult = _("Can't mix: no compatible inputs found!");
Expand Down Expand Up @@ -1489,7 +1523,7 @@ bool CPrivateSendClientSession::MakeCollateralAmounts(const CompactTallyItem& ta
}

// Create denominations by looping through inputs grouped by addresses
bool CPrivateSendClientSession::CreateDenominated(CConnman& connman)
bool CPrivateSendClientSession::CreateDenominated(CAmount nBalanceToDenominate, CConnman& connman)
{
if (!pwalletMain) return false;

Expand All @@ -1513,7 +1547,7 @@ bool CPrivateSendClientSession::CreateDenominated(CConnman& connman)
bool fCreateMixingCollaterals = !pwalletMain->HasCollateralInputs();

for (const auto& item : vecTally) {
if (!CreateDenominated(item, fCreateMixingCollaterals, connman)) continue;
if (!CreateDenominated(nBalanceToDenominate, item, fCreateMixingCollaterals, connman)) continue;
return true;
}

Expand All @@ -1522,7 +1556,7 @@ bool CPrivateSendClientSession::CreateDenominated(CConnman& connman)
}

// Create denominations
bool CPrivateSendClientSession::CreateDenominated(const CompactTallyItem& tallyItem, bool fCreateMixingCollaterals, CConnman& connman)
bool CPrivateSendClientSession::CreateDenominated(CAmount nBalanceToDenominate, const CompactTallyItem& tallyItem, bool fCreateMixingCollaterals, CConnman& connman)
{
if (!pwalletMain) return false;

Expand All @@ -1532,7 +1566,7 @@ bool CPrivateSendClientSession::CreateDenominated(const CompactTallyItem& tallyI
CAmount nValueLeft = tallyItem.nAmount;
nValueLeft -= CPrivateSend::GetCollateralAmount(); // leave some room for fees

LogPrintf("CPrivateSendClientSession::CreateDenominated -- 0 - %s nValueLeft: %f\n", CBitcoinAddress(tallyItem.txdest).ToString(), (float)nValueLeft / COIN);
LogPrint("privatesend", "CPrivateSendClientSession::CreateDenominated -- 0 - %s nValueLeft: %f\n", CBitcoinAddress(tallyItem.txdest).ToString(), (float)nValueLeft / COIN);

// ****** Add an output for mixing collaterals ************ /

Expand All @@ -1544,57 +1578,60 @@ bool CPrivateSendClientSession::CreateDenominated(const CompactTallyItem& tallyI

// ****** Add outputs for denoms ************ /

// try few times - skipping smallest denoms first if there are too many of them already, if failed - use them too
int nOutputsTotal = 0;
bool fSkip = true;
do {
std::vector<CAmount> vecStandardDenoms = CPrivateSend::GetStandardDenominations();

for (auto it = vecStandardDenoms.rbegin(); it != vecStandardDenoms.rend(); ++it) {
CAmount nDenomValue = *it;

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 (privateSendClient.IsDenomSkipped(nDenomValue)) {
strAutoDenomResult = strprintf(_("Too many %f denominations, skipping."), (float)nDenomValue / COIN);
LogPrintf("CPrivateSendClientSession::CreateDenominated -- %s\n", strAutoDenomResult);
continue;
}

// find new denoms to skip if any (ignore the largest one)
if (nDenomValue != vecStandardDenoms.front() && pwalletMain->CountInputsWithAmount(nDenomValue) > DENOMS_COUNT_MAX) {
strAutoDenomResult = strprintf(_("Too many %f denominations, removing."), (float)nDenomValue / COIN);
LogPrintf("CPrivateSendClientSession::CreateDenominated -- %s\n", strAutoDenomResult);
privateSendClient.AddSkippedDenom(nDenomValue);
continue;
}
}
bool fAddFinal = true;
std::vector<CAmount> vecStandardDenoms = CPrivateSend::GetStandardDenominations();

int nOutputs = 0;
for (auto it = vecStandardDenoms.rbegin(); it != vecStandardDenoms.rend(); ++it) {
CAmount nDenomValue = *it;

// add each output up to 11 times until it can't be added again
while (nValueLeft - nDenomValue >= 0 && nOutputs <= 10) {
CScript scriptDenom = keyHolderStorageDenom.AddKey(pwalletMain);
// Note: denoms are skipped if there are already nPrivateSendDenoms of them
// and there are still larger denoms which can be used for mixing

vecSend.push_back((CRecipient){scriptDenom, nDenomValue, false});
// check skipped denoms
if (privateSendClient.IsDenomSkipped(nDenomValue)) {
strAutoDenomResult = strprintf(_("Too many %f denominations, skipping."), (float)nDenomValue / COIN);
LogPrint("privatesend", "CPrivateSendClientSession::CreateDenominated -- %s\n", strAutoDenomResult);
continue;
}

//increment outputs and subtract denomination amount
nOutputs++;
nValueLeft -= nDenomValue;
LogPrintf("CPrivateSendClientSession::CreateDenominated -- 1 - totalOutputs: %d, nOutputsTotal: %d, nOutputs: %d, nValueLeft: %f\n", nOutputsTotal + nOutputs, nOutputsTotal, nOutputs, (float)nValueLeft / COIN);
}
// find new denoms to skip if any (ignore the largest one)
if (nDenomValue != vecStandardDenoms.front() && pwalletMain->CountInputsWithAmount(nDenomValue) > privateSendClient.nPrivateSendDenoms) {
strAutoDenomResult = strprintf(_("Too many %f denominations, removing."), (float)nDenomValue / COIN);
LogPrint("privatesend", "CPrivateSendClientSession::CreateDenominated -- %s\n", strAutoDenomResult);
privateSendClient.AddSkippedDenom(nDenomValue);
continue;
}

nOutputsTotal += nOutputs;
if (nValueLeft == 0) break;
int nOutputs = 0;

auto needMoreOutputs = [&]() {
bool fRegular = (nValueLeft >= nDenomValue && nBalanceToDenominate >= nDenomValue);
bool fFinal = (fAddFinal
&& nValueLeft >= nDenomValue
&& nBalanceToDenominate > 0
&& nBalanceToDenominate < nDenomValue);
fAddFinal = false; // add final denom only once, only the smalest possible one
return fRegular || fFinal;
};

// add each output up to 11 times until it can't be added again
while (needMoreOutputs() && nOutputs <= 10) {
CScript scriptDenom = keyHolderStorageDenom.AddKey(pwalletMain);

vecSend.push_back((CRecipient){scriptDenom, nDenomValue, false});

//increment outputs and subtract denomination amount
nOutputs++;
nValueLeft -= nDenomValue;
nBalanceToDenominate -= nDenomValue;
LogPrint("privatesend", "CPrivateSendClientSession::CreateDenominated -- 1 - totalOutputs: %d, nOutputsTotal: %d, nOutputs: %d, nValueLeft: %f\n", nOutputsTotal + nOutputs, nOutputsTotal, nOutputs, (float)nValueLeft / COIN);
}
LogPrintf("CPrivateSendClientSession::CreateDenominated -- 2 - 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("CPrivateSendClientSession::CreateDenominated -- 3 - nOutputsTotal: %d, nValueLeft: %f\n", nOutputsTotal, (float)nValueLeft / COIN);

nOutputsTotal += nOutputs;
if (nValueLeft == 0 || nBalanceToDenominate <= 0) break;
}
LogPrint("privatesend", "CPrivateSendClientSession::CreateDenominated -- 2 - nOutputsTotal: %d, nValueLeft: %f\n", nOutputsTotal, (float)nValueLeft / COIN);

// No reasons to create mixing collaterals if we can't create denoms to mix
if (nOutputsTotal == 0) return false;
Expand Down
Loading

0 comments on commit b3ed641

Please sign in to comment.