improve selection
MODIFY
WalletWasabi.Fluent/ViewModels/Wallets/Send/LabelSelectionViewModel.cs
WalletWasabi.Fluent/ViewModels/Wallets/Send/LabelSelectionViewModel.cs
using System.Collections.Generic;
+using System.Collections.Immutable;
using System.Linq;
using NBitcoin;
using ReactiveUI;
@@ -51,9 +52,9 @@ public partial class LabelSelectionViewModel : ViewModelBase
return new[] { _privatePocket };
}
- if (knownByRecipientPockets.Sum(x => x.Amount) >= _targetAmount)
+ if (GetBestKnownByRecipientPockets(knownByRecipientPockets, _targetAmount, recipient) is { } pockets)
{
- return knownByRecipientPockets;
+ return pockets;
}
if (knownPockets.Sum(x => x.Amount) >= _targetAmount)
@@ -84,6 +85,61 @@ public partial class LabelSelectionViewModel : ViewModelBase
return _allPockets.ToArray();
}
+ private Pocket[]? GetBestKnownByRecipientPockets(Pocket[] knownByRecipientPockets, Money targetAmount, SmartLabel recipient)
+ {
+ var rankedPockets =
+ knownByRecipientPockets
+ .OrderByDescending(pocket => pocket.Labels.Count(recipient.Contains))
+ .ThenBy(pocket => pocket.Labels.Count())
+ .ToArray();
+
+ if (rankedPockets.FirstOrDefault(pocket => pocket.Amount >= targetAmount) is { } pocket)
+ {
+ return new[] { pocket };
+ }
+
+ var privacyRankedPockets =
+ knownByRecipientPockets
+ .Select(pocket =>
+ {
+ var containedLabelsCount = pocket.Labels.Count(recipient.Contains);
+ var notContainedLabelsCount = pocket.Labels.Count() - containedLabelsCount;
+ return (acceptabilityIndex: (double)notContainedLabelsCount / containedLabelsCount, pocket);
+ })
+ .OrderBy(tup => tup.acceptabilityIndex) // the lower the better
+ .ThenBy(tup => tup.pocket.Labels.Count())
+ .ThenByDescending(tup => tup.pocket.Amount)
+ .Select(tup => tup.pocket)
+ .ToArray();
+
+ var pockets = new List<Pocket>();
+ foreach (var p in privacyRankedPockets)
+ {
+ if (pockets.Sum(x => x.Amount) < targetAmount)
+ {
+ pockets.Add(p);
+ }
+ else
+ {
+ break;
+ }
+ }
+
+ foreach (var p in pockets.OrderBy(x => x.Amount).ToImmutableArray())
+ {
+ if (pockets.Sum(x => x.Amount) - p.Amount >= targetAmount)
+ {
+ pockets.Remove(p);
+ }
+ else
+ {
+ break;
+ }
+ }
+
+ return pockets.Any() ? pockets.ToArray() : null;
+ }
+
public Pocket[] GetUsedPockets()
{
var pocketsToReturn = NonPrivatePockets.Where(x => x.Labels.All(label => LabelsWhiteList.Any(labelViewModel => labelViewModel.Value == label))).ToList();

MODIFY
WalletWasabi.Tests/UnitTests/UserInterfaceTest/PocketSelectionTests.cs
WalletWasabi.Tests/UnitTests/UserInterfaceTest/PocketSelectionTests.cs
@@ -652,6 +652,62 @@ public class PocketSelectionTests
Assert.Contains(pocket4, output);
}
+ [Fact]
+ public void AutoSelectKnownByMultipleRecipientPockets()
+ {
+ var selection = new LabelSelectionViewModel(Money.Parse("1.0"));
+
+ var pockets = new List<Pocket>();
+ pockets.AddPocket(0.8M, out var pocket1, CoinPocketHelper.PrivateFundsText);
+ pockets.AddPocket(1.1M, out var pocket2, "Dan");
+ pockets.AddPocket(0.5M, out var pocket3, CoinPocketHelper.UnlabelledFundsText);
+ pockets.AddPocket(1.1M, out var pocket4, "David", "Lucas", "Dan");
+ pockets.AddPocket(1.1M, out var pocket5, "David");
+ pockets.AddPocket(1.1M, out var pocket6, "Lucas");
+ pockets.AddPocket(1.1M, out var pocket7, "David", "Lucas", "Dan", "Roland");
+ pockets.AddPocket(1.1M, out var pocket8, "David", "Dan");
+
+ selection.Reset(pockets.ToArray());
+
+ var output = selection.AutoSelectPockets("David, Lucas");
+ Assert.DoesNotContain(pocket1, output);
+ Assert.DoesNotContain(pocket2, output);
+ Assert.DoesNotContain(pocket3, output);
+ Assert.Contains(pocket4, output);
+ Assert.DoesNotContain(pocket5, output);
+ Assert.DoesNotContain(pocket6, output);
+ Assert.DoesNotContain(pocket7, output);
+ Assert.DoesNotContain(pocket8, output);
+ }
+
+ [Fact]
+ public void AutoSelectMultipleKnownByMultipleRecipientPockets()
+ {
+ var selection = new LabelSelectionViewModel(Money.Parse("1.0"));
+
+ var pockets = new List<Pocket>();
+ pockets.AddPocket(0.8M, out var pocket1, CoinPocketHelper.PrivateFundsText);
+ pockets.AddPocket(1.1M, out var pocket2, "Dan");
+ pockets.AddPocket(0.5M, out var pocket3, CoinPocketHelper.UnlabelledFundsText);
+ pockets.AddPocket(0.5M, out var pocket4, "David", "Lucas", "Dan");
+ pockets.AddPocket(0.6M, out var pocket5, "David");
+ pockets.AddPocket(0.2M, out var pocket6, "Lucas");
+ pockets.AddPocket(0.1M, out var pocket7, "David", "Lucas", "Dan", "Roland");
+ pockets.AddPocket(0.3M, out var pocket8, "David", "Dan");
+
+ selection.Reset(pockets.ToArray());
+
+ var output = selection.AutoSelectPockets("David, Lucas");
+ Assert.DoesNotContain(pocket1, output);
+ Assert.DoesNotContain(pocket2, output);
+ Assert.DoesNotContain(pocket3, output);
+ Assert.Contains(pocket4, output);
+ Assert.Contains(pocket5, output);
+ Assert.DoesNotContain(pocket6, output);
+ Assert.DoesNotContain(pocket7, output);
+ Assert.DoesNotContain(pocket8, output);
+ }
+
[Fact]
public void NotEnoughSelectedWhenSameLabelFoundInSeveralPockets()
{

improve selection