Make sure clusters are set after anonsets
MODIFY
WalletWasabi.Tests/UnitTests/Transactions/TransactionProcessorTests.cs
WalletWasabi.Tests/UnitTests/Transactions/TransactionProcessorTests.cs
@@ -1100,7 +1100,7 @@ namespace WalletWasabi.Tests.UnitTests.Transactions
return new OutPoint(RandomUtils.GetUInt256(), 0);
}
- private async Task<TransactionProcessor> CreateTransactionProcessorAsync([CallerFilePath] string callerFilePath = "", [CallerMemberName] string callerMemberName = "")
+ private async Task<TransactionProcessor> CreateTransactionProcessorAsync(int privacyLevelThreshold = 100, [CallerFilePath] string callerFilePath = "", [CallerMemberName] string callerMemberName = "")
{
var keyManager = KeyManager.CreateNew(out _, "password");
keyManager.AssertCleanKeysIndexed();
@@ -1113,7 +1113,8 @@ namespace WalletWasabi.Tests.UnitTests.Transactions
return new TransactionProcessor(
txStore,
keyManager,
- Money.Coins(0.0001m));
+ Money.Coins(0.0001m),
+ privacyLevelThreshold);
}
}
}

MODIFY
WalletWasabi/Blockchain/Analysis/Clustering/Cluster.cs
WalletWasabi/Blockchain/Analysis/Clustering/Cluster.cs
@@ -29,8 +29,6 @@ namespace WalletWasabi.Blockchain.Analysis.Clustering
private set => RaiseAndSetIfChanged(ref _labels, value);
}
- public int Size => Keys.Count;
-
private object Lock { get; }
private List<HdPubKey> Keys { get; set; }
private HashSet<HdPubKey> KeysSet { get; set; }
@@ -70,6 +68,8 @@ namespace WalletWasabi.Blockchain.Analysis.Clustering
Labels = SmartLabel.Merge(Keys.Select(x => x.Label));
}
+ public override string ToString() => Labels;
+
#region EqualityAndComparison
public override bool Equals(object? obj) => Equals(obj as Cluster);

MODIFY
WalletWasabi/Blockchain/Keys/HdPubKey.cs
WalletWasabi/Blockchain/Keys/HdPubKey.cs
@@ -16,6 +16,7 @@ namespace WalletWasabi.Blockchain.Keys
{
private Cluster _cluster;
private int _anonymitySet = int.MaxValue;
+
public HdPubKey(PubKey pubKey, KeyPath fullKeyPath, SmartLabel label, KeyState keyState)
{
PubKey = Guard.NotNull(nameof(pubKey), pubKey);
@@ -61,6 +62,7 @@ namespace WalletWasabi.Blockchain.Keys
get => _anonymitySet;
set => RaiseAndSetIfChanged(ref _anonymitySet, value);
}
+
public HashSet<SmartCoin> Coins { get; } = new HashSet<SmartCoin>();
[JsonProperty(Order = 1)]

MODIFY
WalletWasabi/Blockchain/TransactionOutputs/CoinsRegistry.cs
WalletWasabi/Blockchain/TransactionOutputs/CoinsRegistry.cs
@@ -11,7 +11,7 @@ namespace WalletWasabi.Blockchain.TransactionOutputs
{
public class CoinsRegistry : ICoinsView
{
- public CoinsRegistry(int privacyLevelThreshold)
+ public CoinsRegistry()
{
Coins = new HashSet<SmartCoin>();
SpentCoins = new HashSet<SmartCoin>();
@@ -19,7 +19,6 @@ namespace WalletWasabi.Blockchain.TransactionOutputs
LatestSpentCoinsSnapshot = new HashSet<SmartCoin>();
InvalidateSnapshot = false;
CoinsByOutPoint = new Dictionary<OutPoint, HashSet<SmartCoin>>();
- PrivacyLevelThreshold = privacyLevelThreshold;
Lock = new object();
}
@@ -30,7 +29,6 @@ namespace WalletWasabi.Blockchain.TransactionOutputs
private HashSet<SmartCoin> SpentCoins { get; }
private HashSet<SmartCoin> LatestSpentCoinsSnapshot { get; set; }
private Dictionary<OutPoint, HashSet<SmartCoin>> CoinsByOutPoint { get; }
- private int PrivacyLevelThreshold { get; }
public bool IsEmpty => !AsCoinsView().Any();
@@ -162,15 +160,6 @@ namespace WalletWasabi.Blockchain.TransactionOutputs
{
InvalidateSnapshot = true;
SpentCoins.Add(spentCoin);
- var createdCoins = CreatedByNoLock(spentCoin.SpenderTransaction.GetHash());
- foreach (var newCoin in createdCoins)
- {
- if (newCoin.HdPubKey.AnonymitySet < PrivacyLevelThreshold)
- {
- spentCoin.HdPubKey.Cluster.Merge(newCoin.HdPubKey.Cluster);
- newCoin.HdPubKey.Cluster = spentCoin.HdPubKey.Cluster;
- }
- }
}
}
}

MODIFY
WalletWasabi/Blockchain/TransactionProcessing/TransactionProcessor.cs
WalletWasabi/Blockchain/TransactionProcessing/TransactionProcessor.cs
@@ -17,12 +17,13 @@ namespace WalletWasabi.Blockchain.TransactionProcessing
AllTransactionStore transactionStore,
KeyManager keyManager,
Money dustThreshold,
- int privacyLevelThreshold = 100)
+ int privacyLevelThreshold)
{
TransactionStore = Guard.NotNull(nameof(transactionStore), transactionStore);
KeyManager = Guard.NotNull(nameof(keyManager), keyManager);
DustThreshold = Guard.NotNull(nameof(dustThreshold), dustThreshold);
- Coins = new CoinsRegistry(privacyLevelThreshold);
+ Coins = new CoinsRegistry();
+ PrivacyLevelThreshold = privacyLevelThreshold;
}
public event EventHandler<ProcessedResult>? WalletRelevantTransactionProcessed;
@@ -39,6 +40,7 @@ namespace WalletWasabi.Blockchain.TransactionProcessing
public int QueuedTxCount { get; private set; }
public int QueuedProcessedTxCount { get; private set; }
+ public int PrivacyLevelThreshold { get; }
#endregion Progress
@@ -248,10 +250,10 @@ namespace WalletWasabi.Blockchain.TransactionProcessing
}
// Calculate anonymity sets.
- foreach (var coin in result.NewlyReceivedCoins)
+ foreach (var newCoin in result.NewlyReceivedCoins)
{
// Get the anonymity set of i-th output in the transaction.
- var anonset = tx.Transaction.GetAnonymitySet(coin.Index);
+ var anonset = tx.Transaction.GetAnonymitySet(newCoin.Index);
// If we provided inputs to the transaction.
if (tx.WalletInputs.Any())
{
@@ -264,7 +266,16 @@ namespace WalletWasabi.Blockchain.TransactionProcessing
anonset += tx.WalletInputs.Select(x => x.HdPubKey).Min(x => x.AnonymitySet) - 1;
}
- coin.HdPubKey.AnonymitySet = Math.Min(anonset, coin.HdPubKey.AnonymitySet);
+ newCoin.HdPubKey.AnonymitySet = Math.Min(anonset, newCoin.HdPubKey.AnonymitySet);
+
+ // Set clusters.
+ if (newCoin.HdPubKey.AnonymitySet < PrivacyLevelThreshold)
+ {
+ foreach (var spentCoin in result.NewlySpentCoins)
+ {
+ newCoin.HdPubKey.Cluster.Merge(spentCoin.HdPubKey.Cluster);
+ }
+ }
}
}

MODIFY
WalletWasabi/Wallets/Wallet.cs
WalletWasabi/Wallets/Wallet.cs
@@ -393,7 +393,10 @@ namespace WalletWasabi.Wallets
KeyManager.AssertNetworkOrClearBlockState(Network);
Height bestKeyManagerHeight = KeyManager.GetBestHeight();
+ using (BenchmarkLogger.Measure(LogLevel.Info, "Initial Transaction Processing"))
+ {
TransactionProcessor.Process(BitcoinStore.TransactionStore.ConfirmedStore.GetTransactions().TakeWhile(x => x.Height <= bestKeyManagerHeight));
+ }
// Go through the filters and queue to download the matches.
await BitcoinStore.IndexStore.ForeachFiltersAsync(async (filterModel) =>

Make sure clusters are set after anonsets