private IEnumerable <TxoRef> GetRegistrableCoinsNoLock(int maximumInputCountPerPeer, Money feePerInputs, Money amountNeededExceptInputFees, bool allowUnconfirmedZeroLink) { if (!WaitingList.Any()) // To avoid computations. { return(Enumerable.Empty <TxoRef>()); } Func <SmartCoin, bool> confirmationPredicate; if (allowUnconfirmedZeroLink) { confirmationPredicate = x => x.Confirmed || x.Label.StartsWith("ZeroLink", StringComparison.Ordinal); } else { confirmationPredicate = x => x.Confirmed; } var coins = WaitingList .Where(x => x.Value <= DateTimeOffset.UtcNow) .Select(x => x.Key) // Only if registering coins is already allowed. .Where(confirmationPredicate); for (int i = 1; i <= maximumInputCountPerPeer; i++) // The smallest number of coins we can register the better it is. { var linq = coins.GetPermutations(i); linq = linq.Where(x => x.Sum(y => y.Amount) >= amountNeededExceptInputFees + (feePerInputs * i)); // If the sum reaches the minimum amount. if (i == 1) // If only one coin is to be registered. { // Prefer the largest one, so more mixing volume is more likely. linq = linq.OrderByDescending(x => x.Sum(y => y.Amount)); // Try to register with the smallest anonymity set, so new unmixed coins come to the mix. linq = linq.OrderBy(x => x.Sum(y => y.AnonymitySet)); } else // Else coin merging will happen. { // Prefer the lowest amount sum, so perfect mix should be more likely. linq = linq.OrderBy(x => x.Sum(y => y.Amount)); // Try to register the largest anonymity set, so red and green coins input merging should be less likely. linq = linq.OrderByDescending(x => x.Sum(y => y.AnonymitySet)); } linq = linq.OrderBy(x => x.Count(y => y.Confirmed == false)); // Where the lowest amount of unconfirmed coins there are. IEnumerable <SmartCoin> best = linq.FirstOrDefault(); if (best != default) { return(best.Select(x => x.GetTxoRef()).ToArray()); } } return(Enumerable.Empty <TxoRef>()); // Inputs are too small, max input to be registered is reached. }
public bool AnyCoinsQueued() { lock (StateLock) { if (WaitingList.Any()) { return(true); } foreach (var coins in Rounds.Select(x => x.CoinsRegistered)) { if (coins.Any()) { return(true); } } return(false); } }
public IEnumerable <TxoRef> GetRegistrableCoins(int maximumInputCountPerPeer, Money denomination, Money feePerInputs, Money feePerOutputs) { lock (StateLock) { if (!WaitingList.Any()) // To avoid computations. { return(Enumerable.Empty <TxoRef>()); } Money amountNeededExceptInputFees = denomination + (feePerOutputs * 2); var confirmedResult = GetRegistrableCoinsNoLock(maximumInputCountPerPeer, feePerInputs, amountNeededExceptInputFees, allowUnconfirmedZeroLink: false); if (confirmedResult.Any()) { return(confirmedResult); } else { return(GetRegistrableCoinsNoLock(maximumInputCountPerPeer, feePerInputs, amountNeededExceptInputFees, allowUnconfirmedZeroLink: true)); } } }
private IEnumerable <TxoRef> GetRegistrableCoinsNoLock(int maximumInputCountPerPeer, Money feePerInputs, Money amountNeededExceptInputFees, bool allowUnconfirmedZeroLink) { if (!WaitingList.Any()) // To avoid computations. { return(Enumerable.Empty <TxoRef>()); } Func <SmartCoin, bool> confirmationPredicate; if (allowUnconfirmedZeroLink) { confirmationPredicate = x => x.Confirmed || x.Label.StartsWith("ZeroLink", StringComparison.Ordinal); } else { confirmationPredicate = x => x.Confirmed; } var coins = WaitingList .Where(x => x.Value <= DateTimeOffset.UtcNow) .Select(x => x.Key) // Only if registering coins is already allowed. .Where(confirmationPredicate); 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) { return(best.Select(x => x.GetTxoRef()).ToArray()); } } return(Enumerable.Empty <TxoRef>()); // Inputs are too small, max input to be registered is reached. }
private IEnumerable <TxoRef> GetRegistrableCoinsNoLock(int maximumInputCountPerPeer, Money feePerInputs, Money amountNeededExceptInputFees, bool allowUnconfirmedZeroLink) { if (!WaitingList.Any()) // To avoid computations. { return(Enumerable.Empty <TxoRef>()); } Func <SmartCoin, bool> confirmationPredicate; if (allowUnconfirmedZeroLink) { confirmationPredicate = x => x.Confirmed || x.Label.StartsWith("ZeroLink", StringComparison.Ordinal); } else { confirmationPredicate = x => x.Confirmed; } var coins = WaitingList .Where(x => x.Value <= DateTimeOffset.UtcNow) .Select(x => x.Key) // Only if registering coins is already allowed. .Where(confirmationPredicate) .ToList(); // So to not redo it in every cycle. bool lazyMode = false; for (int i = 1; i <= maximumInputCountPerPeer; i++) // The smallest number of coins we can register the better it is. { List <IEnumerable <SmartCoin> > coinGroups; Money amountNeeded = amountNeededExceptInputFees + (feePerInputs * i); // If the sum reaches the minimum amount. if (lazyMode) // Do the largest valid combination. { IEnumerable <SmartCoin> highestValueEnumeration = coins.OrderByDescending(x => x.Amount).Take(i); if (highestValueEnumeration.Sum(x => x.Amount) >= amountNeeded) { coinGroups = new List <IEnumerable <SmartCoin> > { highestValueEnumeration }; } else { coinGroups = new List <IEnumerable <SmartCoin> >(); } } else { DateTimeOffset start = DateTimeOffset.UtcNow; coinGroups = coins.GetPermutations(i, amountNeeded).ToList(); if (DateTimeOffset.UtcNow - start > TimeSpan.FromMilliseconds(10)) // If the permutations took long then then if there's a nextTime, calculating permutations would be too CPU intensive. { lazyMode = true; } } if (i == 1) // If only one coin is to be registered. { // Prefer the largest one, so more mixing volume is more likely. coinGroups = coinGroups.OrderByDescending(x => x.Sum(y => y.Amount)).ToList(); // Try to register with the smallest anonymity set, so new unmixed coins come to the mix. coinGroups = coinGroups.OrderBy(x => x.Sum(y => y.AnonymitySet)).ToList(); } else // Else coin merging will happen. { // Prefer the lowest amount sum, so perfect mix should be more likely. coinGroups = coinGroups.OrderBy(x => x.Sum(y => y.Amount)).ToList(); // Try to register the largest anonymity set, so red and green coins input merging should be less likely. coinGroups = coinGroups.OrderByDescending(x => x.Sum(y => y.AnonymitySet)).ToList(); } coinGroups = coinGroups.OrderBy(x => x.Count(y => y.Confirmed == false)).ToList(); // Where the lowest amount of unconfirmed coins there are. IEnumerable <SmartCoin> best = coinGroups.FirstOrDefault(); if (best != default) { var bestSet = best.ToHashSet(); // -- OPPORTUNISTIC CONSOLIDATION -- // https://github.com/zkSNACKs/WalletWasabi/issues/1651 if (bestSet.Count < maximumInputCountPerPeer) // Ensure limits. { // Generating toxic change leads to mass merging so it's better to merge sooner in coinjoin than the user do it himself in a non-CJ. // The best selection's anonset should not be lowered by this merge. int bestMinAnonset = bestSet.Min(x => x.AnonymitySet); var bestSum = Money.Satoshis(bestSet.Sum(x => x.Amount)); if (!bestSum.Almost(amountNeeded, Money.Coins(0.0001m)) && // Otherwise it wouldn't generate change so consolidation would make no sense. bestMinAnonset > 1) // Red coins should never be merged. { IEnumerable <SmartCoin> coinsThoseCanBeConsolidated = coins .Except(bestSet) // Get all the registrable coins, except the already chosen ones. .Where(x => x.AnonymitySet >= bestMinAnonset && // The anonset must be at least equal to the bestSet's anonset so we don't ruin the change's after mix anonset. x.AnonymitySet > 1 && // Red coins should never be merged. x.Amount <amountNeeded && // The amount need to be smaller than the amountNeeded (so to make sure this is toxic change.) (bestSum + x.Amount)> amountNeeded) // Sanity check that the amount added don't ruin the registration. .OrderBy(x => x.Amount); // Choose the smallest ones. if (coinsThoseCanBeConsolidated.Count() > 1) // Because the last one change should not be circulating, ruining privacy. { var bestCoinToAdd = coinsThoseCanBeConsolidated.First(); bestSet.Add(bestCoinToAdd); } } } return(bestSet.Select(x => x.GetTxoRef()).ToArray()); } } return(Enumerable.Empty <TxoRef>()); // Inputs are too small, max input to be registered is reached. }
public IEnumerable <OutPoint> GetRegistrableCoins(int maximumInputCountPerPeer, Money denomination, Money feePerInputs, Money feePerOutputs) { lock (StateLock) { if (!WaitingList.Any()) // To avoid computations. { return(Enumerable.Empty <OutPoint>()); } Money amountNeededExceptInputFees = denomination + (feePerOutputs * 2); var coins = WaitingList .Where(x => x.Value <= DateTimeOffset.UtcNow) .Select(x => x.Key) // Only if registering coins is already allowed. .Where(x => x.Confirmed) .ToList(); // So to not redo it in every cycle. bool lazyMode = false; for (int i = 1; i <= maximumInputCountPerPeer; i++) // The smallest number of coins we can register the better it is. { List <IEnumerable <SmartCoin> > coinGroups; Money amountNeeded = amountNeededExceptInputFees + (feePerInputs * i); // If the sum reaches the minimum amount. if (lazyMode) // Do the largest valid combination. { IEnumerable <SmartCoin> highestValueEnumeration = coins.OrderByDescending(x => x.Amount).Take(i); coinGroups = highestValueEnumeration.Sum(x => x.Amount) >= amountNeeded ? new List <IEnumerable <SmartCoin> > { highestValueEnumeration } : new List <IEnumerable <SmartCoin> >(); } else { DateTimeOffset start = DateTimeOffset.UtcNow; coinGroups = coins.GetPermutations(i, amountNeeded).ToList(); if (DateTimeOffset.UtcNow - start > TimeSpan.FromMilliseconds(10)) // If the permutations took long then then if there's a nextTime, calculating permutations would be too CPU intensive. { lazyMode = true; } } if (i == 1) // If only one coin is to be registered. { // Prefer the largest one, so more mixing volume is more likely. coinGroups = coinGroups.OrderByDescending(x => x.Sum(y => y.Amount)).ToList(); // Try to register with the smallest anonymity set, so new unmixed coins come to the mix. coinGroups = coinGroups.OrderBy(x => x.Sum(y => y.HdPubKey.AnonymitySet)).ToList(); } else // Else coin merging will happen. { // Prefer the lowest amount sum, so perfect mix should be more likely. coinGroups = coinGroups.OrderBy(x => x.Sum(y => y.Amount)).ToList(); // Try to register the largest anonymity set, so red and green coins input merging should be less likely. coinGroups = coinGroups.OrderByDescending(x => x.Sum(y => y.HdPubKey.AnonymitySet)).ToList(); } coinGroups = coinGroups.OrderBy(x => x.Count(y => y.Confirmed == false)).ToList(); // Where the lowest amount of unconfirmed coins there are. IEnumerable <SmartCoin> best = coinGroups.FirstOrDefault(); if (best is { })
private IEnumerable <TxoRef> GetRegistrableCoinsNoLock(int maximumInputCountPerPeer, Money feePerInputs, Money amountNeededExceptInputFees, bool allowUnconfirmedZeroLink) { if (!WaitingList.Any()) // To avoid computations. { return(Enumerable.Empty <TxoRef>()); } Func <SmartCoin, bool> confirmationPredicate; if (allowUnconfirmedZeroLink) { confirmationPredicate = x => x.Confirmed || x.Label.StartsWith("ZeroLink", StringComparison.Ordinal); } else { confirmationPredicate = x => x.Confirmed; } var coins = WaitingList .Where(x => x.Value <= DateTimeOffset.UtcNow) .Select(x => x.Key) // Only if registering coins is already allowed. .Where(confirmationPredicate) .ToList(); // So to not redo it in every cycle. bool perfectMode = true; for (int i = 1; i <= maximumInputCountPerPeer; i++) // The smallest number of coins we can register the better it is. { List <IEnumerable <SmartCoin> > coinGroups; Money amountNeeded = amountNeededExceptInputFees + (feePerInputs * i); // If the sum reaches the minimum amount. if (perfectMode) { DateTimeOffset start = DateTimeOffset.UtcNow; coinGroups = coins.GetPermutations(i, amountNeeded).ToList(); if (DateTimeOffset.UtcNow - start > TimeSpan.FromMilliseconds(10)) // If the permutations took long then then if there's a nextTime, calculating permutations would be too CPU intensive. { perfectMode = false; } } else // Do the largest valid combination. { IEnumerable <SmartCoin> highestValueEnumeration = coins.OrderByDescending(x => x.Amount).Take(i); if (highestValueEnumeration.Sum(x => x.Amount) >= amountNeeded) { coinGroups = new List <IEnumerable <SmartCoin> > { highestValueEnumeration }; } else { coinGroups = new List <IEnumerable <SmartCoin> >(); } } if (i == 1) // If only one coin is to be registered. { // Prefer the largest one, so more mixing volume is more likely. coinGroups = coinGroups.OrderByDescending(x => x.Sum(y => y.Amount)).ToList(); // Try to register with the smallest anonymity set, so new unmixed coins come to the mix. coinGroups = coinGroups.OrderBy(x => x.Sum(y => y.AnonymitySet)).ToList(); } else // Else coin merging will happen. { // Prefer the lowest amount sum, so perfect mix should be more likely. coinGroups = coinGroups.OrderBy(x => x.Sum(y => y.Amount)).ToList(); // Try to register the largest anonymity set, so red and green coins input merging should be less likely. coinGroups = coinGroups.OrderByDescending(x => x.Sum(y => y.AnonymitySet)).ToList(); } coinGroups = coinGroups.OrderBy(x => x.Count(y => y.Confirmed == false)).ToList(); // Where the lowest amount of unconfirmed coins there are. IEnumerable <SmartCoin> best = coinGroups.FirstOrDefault(); if (best != default) { return(best.Select(x => x.GetTxoRef()).ToArray()); } } return(Enumerable.Empty <TxoRef>()); // Inputs are too small, max input to be registered is reached. }