Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add more variance to coin selection in PS mixing #2261

Merged
merged 5 commits into from
Sep 5, 2018

Conversation

UdjinM6
Copy link

@UdjinM6 UdjinM6 commented Sep 4, 2018

This should make it harder to analyse mixing sessions. See individual commits, should be self-explanatory. Client side only, requires no protobump.

Note: this includes #2259 , will rebase after #2259 is merged.

@PastaPastaPasta @Antti-Kaikkonen

@UdjinM6 UdjinM6 added this to the 12.4 milestone Sep 4, 2018
@PastaPastaPasta
Copy link
Member

PastaPastaPasta commented Sep 4, 2018

concept ACK, I think the selection could be a little bit better than this in such a way that any input could be selected (rather than highest, middlest(¯\(ツ)/¯), lowest). But with this in conjunction with 8fe3811, this should be a major improvement, while not decreasing speed much. The only situation I am worried about here is the mixing of a single input, but even that should be fine with 8fe3811

Copy link
Member

@PastaPastaPasta PastaPastaPasta left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See inline: Spacing after if and for

Looks like these files as a whole need a redo, I might do that after this gets merged

// 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)) {
LogPrintf("CPrivateSendClientSession::JoinExistingQueue -- Couldn't match %d denominations %d %d (%s)\n", dsq.nInputCount, vecBits.front(), dsq.nDenom, CPrivateSend::GetDenominationsToString(dsq.nDenom));
if(!pwalletMain->SelectCoinsByDenominations(dsq.nDenom, nMinAmount, nMaxAmount, vecTxDSInTmp, vCoinsTmp, nValueInTmp, 0, privateSendClient.nPrivateSendRounds, true)) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if( -> if ( ;)

if (privateSendClient.nLiquidityProvider || fMixLowest) {
// Try to use only inputs with the same number of rounds, from low to high
while (true) {
for(int i = nRoundStart; i < privateSendClient.nPrivateSendRounds; i++) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

for( -> for (

// Try to use only inputs with the same number of rounds, from low to high
while (true) {
for(int i = nRoundStart; i < privateSendClient.nPrivateSendRounds; i++) {
if(PrepareDenominate(i, i + 1, strError, vecTxDSInRet, vecTxOutRet)) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if( -> if (

return SendDenominate(vecTxDSInRet, vecTxOutRet, connman);
// Try to use only inputs with the same number of rounds, from high to low
while (true) {
for(int i = nRoundStart; i > 0; i--) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

for( -> for (

// Try to use only inputs with the same number of rounds, from high to low
while (true) {
for(int i = nRoundStart; i > 0; i--) {
if(PrepareDenominate(i - 1, i, strError, vecTxDSInRet, vecTxOutRet)) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if( -> if (

@@ -3037,6 +3039,7 @@ bool CWallet::SelectCoinsByDenominations(int nDenom, CAmount nValueMin, CAmount
{
// masternode-like input should not be selected by AvailableCoins now anyway
//if(out.tx->vout[out.i].nValue == 1000*COIN) continue;
if(fNoDuplicateTxIds && mapRecentTxIds.find(out.tx->GetHash()) != mapRecentTxIds.end()) continue;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if( -> if (

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm following the surrounding code format here intentionally (to improve readability).

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree on that, if a general code cleanup is needed for the touched code, that's better in a separate PR. If a small code cleanup is enough, e.g. in just the line that is being touched, then in the same commit is ok IMHO.

If failed when started from the middle try mixing from the edges right away.

Note: liqudity providers always start from 0
@UdjinM6
Copy link
Author

UdjinM6 commented Sep 5, 2018

Rebased and force-pushed.

Re all the formatting comments: new line has such for readability reasons and I didn't fix code style in others (old ones) because of the "do not mix functionality and refactoring in one PR" principle https://github.com/dashpay/dash/blob/master/CONTRIBUTING.md#pull-request-philosophy

// Note: liqudity providers always start from 0
int nRoundStart = GetRandInt(4) == 0
? (privateSendClient.nLiquidityProvider ? 0 : (privateSendClient.nPrivateSendRounds / 2))
: (fMixLowest ? 0 : privateSendClient.nPrivateSendRounds);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you convert this into if/else statements? Hard to read and using if/else would make this easier.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not exactly if/else but should be simpler to read now imo.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about this?

diff --git a/src/privatesend-client.cpp b/src/privatesend-client.cpp
index bf1d60f2f..ac0f74fe1 100644
--- a/src/privatesend-client.cpp
+++ b/src/privatesend-client.cpp
@@ -1185,9 +1185,13 @@ bool CPrivateSendClientSession::SubmitDenominate(CConnman& connman)
     // lean towards edges but still mix starting from the middle someties
     // Note: liqudity providers always start from 0
     bool fScanFromTheMiddle = (privateSendClient.nLiquidityProvider == 0) && (GetRandInt(4) == 0);
-    int nRoundStart = fScanFromTheMiddle
-                        ? privateSendClient.nPrivateSendRounds / 2
-                        : (fMixLowest ? 0 : privateSendClient.nPrivateSendRounds);
+
+    int nRoundStart = privateSendClient.nPrivateSendRounds;
+    if (fScanFromTheMiddle) {
+        nRoundStart = privateSendClient.nPrivateSendRounds / 2;
+    } else if (fMixLowest) {
+        nRoundStart = 0;
+    }
 
     // Submit transaction to the pool if we get here
     if (privateSendClient.nLiquidityProvider || fMixLowest) {

I find this much more clear, as it's immediately visible that there are 3 possible cases. With the nested ternary operators, it's very hard to see this.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, makes sense. replaced with e0b0f4e

{
std::map<uint256, uint8_t> mapRecentTxIds;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why std::map and not std::set?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ooops, initial idea was to count them and then maybe sort etc. but then I figured out that I just need unique ones and... forgot about the map thing completely 🙈 Good catch 👍

@@ -3037,6 +3039,7 @@ bool CWallet::SelectCoinsByDenominations(int nDenom, CAmount nValueMin, CAmount
{
// masternode-like input should not be selected by AvailableCoins now anyway
//if(out.tx->vout[out.i].nValue == 1000*COIN) continue;
if(fNoDuplicateTxIds && mapRecentTxIds.find(out.tx->GetHash()) != mapRecentTxIds.end()) continue;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree on that, if a general code cleanup is needed for the touched code, that's better in a separate PR. If a small code cleanup is enough, e.g. in just the line that is being touched, then in the same commit is ok IMHO.

Copy link

@codablock codablock left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

utACK

Copy link

@nmarley nmarley left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

utACK

@UdjinM6 UdjinM6 merged commit ad31dbb into dashpay:develop Sep 5, 2018
@@ -3037,6 +3039,7 @@ bool CWallet::SelectCoinsByDenominations(int nDenom, CAmount nValueMin, CAmount
{
// 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;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But will not this lead to the situation that most mixing transactions will contain only 1 input from each participant?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, it shouldn't. Even if it does that's fine for privacy.

Copy link

@InhumanPerfection InhumanPerfection Sep 5, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it's very fine for privacy. But mixing time will increase significantly. And mixing will cost much more.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This sounds like a valid concern. I'll have a look.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fees would go up, but I see no situation where a participant would only be able to submit one denom, maybe the first mix when they are all fresh out of create denoms

Copy link

@InhumanPerfection InhumanPerfection Sep 6, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@PastaPastaPasta not only after create denoms.

What we have: in most cases (nRoundStart = privateSendClient.nPrivateSendRounds) mixing function will select inputs with highest round originating only from different Txs.

After "create denoms" the participant will be able to provide from 1 to several inputs (depending on amount he is trying to mix - can be several "create denoms" Txs).

And after every mixing round where participant will be able to provide more than 1 input, on the next round in most cases he will not be able to provide more than 1 input because all inputs with highest round will be from the same Tx.

The number of inputs in this situation will always tends to one.

@Antti-Kaikkonen
Copy link

Antti-Kaikkonen commented Sep 10, 2018

I like the concept but what is the motivation behind randomly choosing to scan from

  • lowest to highest
  • highest to lowest
  • middle to lowest
    ?

Wouldn't this approach make certain denominationsrounds more probable for no apparent reason? For example, the denominationsrounds at the middle and below middle would be chosen at a higher probability than the denominationsrounds slightly above the middle.

I think it might be better and more simple if we sometimes choose a random input instead of scanning. SelectCoinsByDenominations used by PrepareDenominate seems to choose a random input between nPrivateSendRoundsMin and nPrivateSendRoundsMax.

My idea would look something like this: (totally untested)

if (privateSendClient.nLiquidityProvider) {
  //scan from lowest
} else if (GetRandInt(4) == 0) {//pick randomly
  if(PrepareDenominate(0, privateSendClient.nPrivateSendRounds, strError, vecTxDSInRet, vecTxOutRet)) {
    LogPrintf("CPrivateSendClientSession::SubmitDenominate -- Running PrivateSend denominate for %d rounds, success\n", i);
    return SendDenominate(vecTxDSInRet, vecTxOutRet, connman);
  }
} else {
  //scan from highest
}

@UdjinM6
Copy link
Author

UdjinM6 commented Sep 10, 2018

@Antti-Kaikkonen lowest to highest, vice versa or from the middle to whatever edge - all of them are not about denominations but about number of rounds, so no, there should be no preferences for specific denoms I think. Motivation for slightly randomising it for rounds is basically to make it harder to guess what's going on, otherwise you know it's highest to lowest all the time, so maybe someone could use this as a heuristic or smth.

Also, pls see an extension of this idea in #2275 (avoiding single input mix) and an alternative solution in #2278 (mixing rounds with most inputs first).

@Antti-Kaikkonen
Copy link

@UdjinM6 yeah rounds is what I mean and not denominations. I think it might be better to sometimes simply pick a random input rather than changing the scan direction because it will randomly choose from a larger set of inputs.

@UdjinM6
Copy link
Author

UdjinM6 commented Sep 11, 2018

@Antti-Kaikkonen Probably. That was one of the ideas I had before #2278 tbh ;) but then I realized that picking random rounds have to be balanced with the speed of mixing somehow and picking rounds with the most inputs improves mixing speed while also achieving some kind of shuffling/randomization as a free bonus, see PR description there.

@UdjinM6 UdjinM6 deleted the fixps branch November 26, 2020 13:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants