Improve WhiteList
MODIFY
WalletWasabi.Backend/Global.cs
WalletWasabi.Backend/Global.cs
@@ -90,30 +90,31 @@ public class Global : IDisposable
var blockNotifier = HostedServices.Get<BlockNotifier>();
- bool coinVerifierEnabled = CoordinatorParameters.RuntimeCoordinatorConfig.IsCoinVerifierEnabled || roundConfig.IsCoinVerifierEnabledForWW1;
+ var wabiSabiConfig = CoordinatorParameters.RuntimeCoordinatorConfig;
+ bool coinVerifierEnabled = wabiSabiConfig.IsCoinVerifierEnabled || roundConfig.IsCoinVerifierEnabledForWW1;
CoinVerifier? coinVerifier = null;
if (coinVerifierEnabled)
{
try
{
- if (!Uri.TryCreate(CoordinatorParameters.RuntimeCoordinatorConfig.CoinVerifierApiUrl, UriKind.RelativeOrAbsolute, out Uri? url))
+ if (!Uri.TryCreate(wabiSabiConfig.CoinVerifierApiUrl, UriKind.RelativeOrAbsolute, out Uri? url))
{
throw new ArgumentException($"Blacklist API URL is invalid in {nameof(WabiSabiConfig)}.");
}
- if (string.IsNullOrEmpty(CoordinatorParameters.RuntimeCoordinatorConfig.CoinVerifierApiAuthToken))
+ if (string.IsNullOrEmpty(wabiSabiConfig.CoinVerifierApiAuthToken))
{
throw new ArgumentException($"Blacklist API token was not provided in {nameof(WabiSabiConfig)}.");
}
- if (CoordinatorParameters.RuntimeCoordinatorConfig.RiskFlags is null)
+ if (wabiSabiConfig.RiskFlags is null)
{
throw new ArgumentException($"Risk indicators were not provided in {nameof(WabiSabiConfig)}.");
}
HttpClient.BaseAddress = url;
- var coinVerifierApiClient = new CoinVerifierApiClient(CoordinatorParameters.RuntimeCoordinatorConfig.CoinVerifierApiAuthToken, RpcClient.Network, HttpClient);
- var whitelist = await Whitelist.CreateAndLoadFromFileAsync(CoordinatorParameters.WhitelistFilePath, cancel).ConfigureAwait(false);
- coinVerifier = new(CoinJoinIdStore, coinVerifierApiClient, whitelist, CoordinatorParameters.RuntimeCoordinatorConfig);
+ var coinVerifierApiClient = new CoinVerifierApiClient(wabiSabiConfig.CoinVerifierApiAuthToken, RpcClient.Network, HttpClient);
+ var whitelist = await Whitelist.CreateAndLoadFromFileAsync(CoordinatorParameters.WhitelistFilePath, wabiSabiConfig, cancel).ConfigureAwait(false);
+ coinVerifier = new(CoinJoinIdStore, coinVerifierApiClient, whitelist, wabiSabiConfig);
Logger.LogInfo("CoinVerifier created successfully.");
}
catch (Exception exc)
@@ -150,7 +151,7 @@ public class Global : IDisposable
var coinJoinScriptStore = CoinJoinScriptStore.LoadFromFile(CoordinatorParameters.CoinJoinScriptStoreFilePath);
- WabiSabiCoordinator = new WabiSabiCoordinator(CoordinatorParameters, RpcClient, CoinJoinIdStore, coinJoinScriptStore, CoordinatorParameters.RuntimeCoordinatorConfig.IsCoinVerifierEnabled ? coinVerifier : null);
+ WabiSabiCoordinator = new WabiSabiCoordinator(CoordinatorParameters, RpcClient, CoinJoinIdStore, coinJoinScriptStore, wabiSabiConfig.IsCoinVerifierEnabled ? coinVerifier : null);
HostedServices.Register<WabiSabiCoordinator>(() => WabiSabiCoordinator, "WabiSabi Coordinator");
HostedServices.Register<RoundBootstrapper>(() => new RoundBootstrapper(TimeSpan.FromMilliseconds(100), Coordinator), "Round Bootstrapper");

MODIFY
WalletWasabi.Tests/UnitTests/WabiSabi/Backend/UtxoWhitelistTests.cs
WalletWasabi.Tests/UnitTests/WabiSabi/Backend/UtxoWhitelistTests.cs
@@ -31,7 +31,7 @@ public class UtxoWhitelistTests
Assert.NotEqual(currentChangeId, whitelist.ChangeId);
await Task.Delay(1000);
- whitelist.RemoveAllExpired(TimeSpan.FromSeconds(1));
+ whitelist.RemoveAllExpired();
Assert.Equal(0, whitelist.CountInnocents());
}

MODIFY
WalletWasabi/WabiSabi/Backend/Banning/CoinVerifier.cs
WalletWasabi/WabiSabi/Backend/Banning/CoinVerifier.cs
@@ -59,8 +59,7 @@ public class CoinVerifier
using CancellationTokenSource cancellationTokenSource = new(TimeSpan.FromSeconds(30));
using CancellationTokenSource linkedCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationTokenSource.Token, cancellationToken);
- var lastChangeId = Whitelist.ChangeId;
- Whitelist.RemoveAllExpired(WabiSabiConfig.ReleaseFromWhitelistAfter);
+ Whitelist.RemoveAllExpired();
var scriptsToCheck = new HashSet<Script>();
var innocentsCounter = 0;
@@ -95,10 +94,7 @@ public class CoinVerifier
}
}
- if (Whitelist.ChangeId != lastChangeId)
- {
- Whitelist.WriteToFile();
- }
+ await Whitelist.WriteToFileIfChangedAsync().ConfigureAwait(false);
var duration = DateTimeOffset.UtcNow - before;
RequestTimeStatista.Instance.Add("verifier-period", duration);

MODIFY
WalletWasabi/WabiSabi/Backend/Banning/Whitelist.cs
WalletWasabi/WabiSabi/Backend/Banning/Whitelist.cs
using NBitcoin;
+using Nito.AsyncEx;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
@@ -17,11 +18,15 @@ namespace WalletWasabi.WabiSabi.Backend.Banning;
/// </summary>
public class Whitelist
{
- internal Whitelist() : this(Enumerable.Empty<Innocent>(), string.Empty)
+ // Used only in tests.
+ internal Whitelist() : this(
+ Enumerable.Empty<Innocent>(),
+ string.Empty,
+ new WabiSabiConfig() { ReleaseFromWhitelistAfter = TimeSpan.FromSeconds(1) })
{
}
- private Whitelist(IEnumerable<Innocent> innocents, string filePath)
+ private Whitelist(IEnumerable<Innocent> innocents, string filePath, WabiSabiConfig wabiSabiConfig)
{
Innocents = new ConcurrentDictionary<OutPoint, Innocent>(innocents.ToDictionary(k => k.Outpoint, v => v));
if (!string.IsNullOrEmpty(filePath))
@@ -29,12 +34,16 @@ public class Whitelist
IoHelpers.EnsureFileExists(filePath);
}
WhitelistFilePath = filePath;
+ WabiSabiConfig = wabiSabiConfig;
}
- public Guid ChangeId { get; private set; } = Guid.NewGuid();
+ internal Guid ChangeId { get; set; } = Guid.Empty;
+ private Guid LastSavedChangeId { get; set; } = Guid.Empty;
private ConcurrentDictionary<OutPoint, Innocent> Innocents { get; }
private string WhitelistFilePath { get; }
+ private WabiSabiConfig WabiSabiConfig { get; }
+ private AsyncLock FileLock { get; } = new();
public int CountInnocents()
{
@@ -66,9 +75,9 @@ public class Whitelist
return false;
}
- public void RemoveAllExpired(TimeSpan releaseTime)
+ public void RemoveAllExpired()
{
- var allInnocentsToRemove = GetInnocents().Where(innocent => innocent.TimeSpent > releaseTime);
+ var allInnocentsToRemove = Innocents.Values.Where(innocent => innocent.TimeSpent > WabiSabiConfig.ReleaseFromWhitelistAfter);
var removedCounter = allInnocentsToRemove.Select(innocent => TryRelease(innocent.Outpoint)).Count();
if (removedCounter > 0)
{
@@ -78,15 +87,22 @@ public class Whitelist
public bool TryGet(OutPoint utxo, [NotNullWhen(true)] out Innocent? innocent)
{
- return Innocents.TryGetValue(utxo, out innocent);
+ if (!Innocents.TryGetValue(utxo, out innocent))
+ {
+ return false;
}
- public IEnumerable<Innocent> GetInnocents()
+ if (innocent.TimeSpent > WabiSabiConfig.ReleaseFromWhitelistAfter)
{
- return Innocents.Values;
+ TryRelease(innocent.Outpoint);
+ Logger.LogInfo($"1 utxo was expired and removed from Whitelist.");
+ return false;
+ }
+
+ return true;
}
- public static async Task<Whitelist> CreateAndLoadFromFileAsync(string whitelistFilePath, CancellationToken cancel)
+ public static async Task<Whitelist> CreateAndLoadFromFileAsync(string whitelistFilePath, WabiSabiConfig wabiSabiConfig, CancellationToken cancel)
{
var innocents = new List<Innocent>();
try
@@ -115,7 +131,7 @@ public class Whitelist
await File.WriteAllLinesAsync(whitelistFilePath, innocents.Select(innocent => innocent.ToString()), CancellationToken.None).ConfigureAwait(false);
}
}
- var whitelist = new Whitelist(innocents, whitelistFilePath);
+ var whitelist = new Whitelist(innocents, whitelistFilePath, wabiSabiConfig);
var numberOfInnocent = whitelist.CountInnocents();
Logger.LogInfo($"{numberOfInnocent} UTXOs are found in whitelist.");
@@ -125,16 +141,24 @@ public class Whitelist
catch (Exception exc)
{
Logger.LogError("Reading from Whitelist failed with error:", exc);
- return new Whitelist(innocents, whitelistFilePath);
+ return new Whitelist(innocents, whitelistFilePath, wabiSabiConfig);
}
}
- public void WriteToFile()
+ public async Task WriteToFileIfChangedAsync()
{
+ if (ChangeId == LastSavedChangeId)
+ {
+ return;
+ }
+
if (!string.IsNullOrEmpty(WhitelistFilePath))
{
- var toFile = GetInnocents().Select(innocent => innocent.ToString());
- File.WriteAllLines(WhitelistFilePath, toFile);
+ var toFile = Innocents.Values.Select(innocent => innocent.ToString());
+ using (await FileLock.LockAsync().ConfigureAwait(false))
+ {
+ await File.WriteAllLinesAsync(WhitelistFilePath, toFile).ConfigureAwait(false);
+ }
}
}
}

Improve WhiteList