Skip to content
Permalink
Browse files

Implement better mix coin selection

  • Loading branch information...
nopara73 committed Nov 23, 2018
1 parent 98e59e8 commit b2cc202b850c183f33c25d4bdc4171f73187d5e6
Showing with 31 additions and 19 deletions.
  1. +17 −0 WalletWasabi/Extensions/LinqExtensions.cs
  2. +14 −19 WalletWasabi/Models/ChaumianCoinJoin/CcjClientState.cs
@@ -61,5 +61,22 @@ public static bool NotNullAndNotEmpty<T>(this IEnumerable<T> source)
{
return !(source is null) && source.Any();
}

public static IEnumerable<IEnumerable<T>> GetPermutations<T>(this IEnumerable<T> items, int count)
{
int i = 0;
foreach (var item in items)
{
if (count == 1)
yield return new T[] { item };
else
{
foreach (var result in items.Skip(i + 1).GetPermutations(count - 1))
yield return new T[] { item }.Concat(result);
}

++i;
}
}
}
}
@@ -179,33 +179,28 @@ public IEnumerable<(uint256 txid, uint index)> GetRegistrableCoins(int maximumIn
{
lock (StateLock)
{
var coinsToRegister = new List<SmartCoin>();
var amountSoFar = Money.Zero;
Money amountNeededExceptInputFees = denomination + (feePerOutputs * 2);
foreach (SmartCoin coin in WaitingList

var coins = WaitingList
.Where(x => x.Value <= DateTimeOffset.UtcNow)
.Select(x => x.Key) // Only if registering coins is already allowed.
.Where(x => x.Confirmed || x.Label.StartsWith("ZeroLink", StringComparison.Ordinal)) // Where our label contains CoinJoin, CoinJoins can be registered even if not confirmed, our label will likely be CoinJoin only if it was a previous CoinJoin, otherwise the server will refuse us.
.OrderBy(x => x.IsBanned) // First order non-banned coins to first.
.ThenByDescending(x => x.Amount) // Then order by amount.
.ThenByDescending(x => x.Confirmed)) // Then order by the amount ordered ienumerable by confirmation, so first try to register confirmed coins.
{
coinsToRegister.Add(coin);

if (maximumInputCountPerPeer < coinsToRegister.Count)
{
return Enumerable.Empty<(uint256 txid, uint index)>(); // Inputs are too small, max input to be registered is reached.
}
.Where(x => x.Confirmed || x.Label.StartsWith("ZeroLink", StringComparison.Ordinal));

amountSoFar += coin.Amount;
if (amountSoFar >= amountNeededExceptInputFees + (feePerInputs * coinsToRegister.Count))
for (int i = 1; i <= maximumInputCountPerPeer; i++) // The smallest number of coins we can register the better it is.
{
IEnumerable<SmartCoin> best = coins.GetPermutations(i)
.Where(x => x.Sum(y => y.Amount) >= amountNeededExceptInputFees + (feePerInputs * i)) // If the sum reaches the minimum amount.
.OrderBy(x => x.Count(y => y.Confirmed == false)) // Where the lowest amount of unconfirmed coins there are.
.ThenBy(x => x.Sum(y => y.AnonymitySet)) // First try t register with the smallest anonymity set.
.ThenBy(x => x.Sum(y => y.Amount)) // Then the lowest amount, so perfect mix should be more likely.
.FirstOrDefault();
if (best != default)
{
// If input count doesn't reach the max input registration AND there are enough coins queued, then can register to mix.
return coinsToRegister.Select(x => (x.TransactionId, x.Index)).ToArray();
return best.Select(x => (x.TransactionId, x.Index)).ToArray();
}
}

return Enumerable.Empty<(uint256 txid, uint index)>(); // Amount is never reached.
return Enumerable.Empty<(uint256 txid, uint index)>(); // Inputs are too small, max input to be registered is reached.
}
}

0 comments on commit b2cc202

Please sign in to comment.
You can’t perform that action at this time.