Refactor CoinJoinIdStore to factory method
MODIFY
WalletWasabi.Backend/Global.cs
WalletWasabi.Backend/Global.cs
@@ -90,9 +90,7 @@ public class Global : IDisposable
Coordinator = new(RpcClient.Network, blockNotifier, Path.Combine(DataDir, "CcjCoordinator"), RpcClient, roundConfig);
Coordinator.CoinJoinBroadcasted += Coordinator_CoinJoinBroadcasted;
- // First, import all coinjoins to one place, so CoinJoinIdStore can load all into memory during creation
- CoinJoinIdStore.ImportWW1CoinJoinsToWW2(Coordinator.CoinJoinsFilePath, coordinatorParameters.CoinJoinIdStoreFilePath);
- CoinJoinIdStore = new(coordinatorParameters.CoinJoinIdStoreFilePath);
+ CoinJoinIdStore = CoinJoinIdStore.Create(Coordinator.CoinJoinsFilePath, coordinatorParameters.CoinJoinIdStoreFilePath);
HostedServices.Register<WabiSabiCoordinator>(() => new WabiSabiCoordinator(coordinatorParameters, RpcClient, CoinJoinIdStore), "WabiSabi Coordinator");
HostedServices.Register<RoundBootstrapper>(() => new RoundBootstrapper(TimeSpan.FromMilliseconds(100), Coordinator), "Round Bootstrapper");
@@ -106,7 +104,7 @@ public class Global : IDisposable
private void Coordinator_CoinJoinBroadcasted(object? sender, Transaction transaction)
{
- CoinJoinIdStore.Append(transaction.GetHash());
+ CoinJoinIdStore.TryAdd(transaction.GetHash());
}
private async Task InitializeP2pAsync(Network network, EndPoint endPoint, CancellationToken cancel)

MODIFY
WalletWasabi.Tests/UnitTests/WabiSabi/Backend/PostRequests/RegisterInputSuccessTests.cs
WalletWasabi.Tests/UnitTests/WabiSabi/Backend/PostRequests/RegisterInputSuccessTests.cs
@@ -53,7 +53,7 @@ public class RegisterInputSuccessTests
var coin = WabiSabiFactory.CreateCoin(key);
var rpc = WabiSabiFactory.CreatePreconfiguredRpcClient(coin);
var coinJoinIdsStore = new CoinJoinIdStore();
- coinJoinIdsStore.Append(coin.Outpoint.Hash);
+ coinJoinIdsStore.TryAdd(coin.Outpoint.Hash);
using Arena arena = await ArenaBuilder.From(cfg).With(rpc).With(coinJoinIdsStore).CreateAndStartAsync(round);
var minAliceDeadline = DateTimeOffset.UtcNow + cfg.ConnectionConfirmationTimeout * 0.9;

MODIFY
WalletWasabi/WabiSabi/Backend/Rounds/CoinJoinStorage/CoinJoinIdStore.cs
WalletWasabi/WabiSabi/Backend/Rounds/CoinJoinStorage/CoinJoinIdStore.cs
using NBitcoin;
+using System.Collections;
+using System.Collections.Generic;
using System.IO;
using System.Linq;
using WalletWasabi.Logging;
namespace WalletWasabi.WabiSabi.Backend.Rounds.CoinJoinStorage;
-public class CoinJoinIdStore : ICoinJoinIdStore
+public class CoinJoinIdStore : InMemoryCoinJoinIdStore
{
- private InMemoryCoinJoinIdStore InMemoryCoinJoinIdStore { get; set; }
private string CoinJoinIdStoreFilePath { get; set; }
private object FileWriteLock { get; set; } = new();
- public CoinJoinIdStore() : this(string.Empty)
+ public CoinJoinIdStore() : this(Enumerable.Empty<uint256>(), string.Empty)
{
}
- public CoinJoinIdStore(string coinJoinIdStoreFilePath)
+ public CoinJoinIdStore(IEnumerable<uint256> coinjoinIds, string coinJoinIdStoreFilePath) : base(coinjoinIds)
{
CoinJoinIdStoreFilePath = coinJoinIdStoreFilePath;
- InMemoryCoinJoinIdStore = InMemoryCoinJoinIdStore.LoadFromFile(coinJoinIdStoreFilePath);
}
- public void Append(uint256 id)
+ public override bool TryAdd(uint256 id)
{
- if (InMemoryCoinJoinIdStore.TryAdd(id))
+ if (base.TryAdd(id))
{
try
{
lock (FileWriteLock)
{
File.AppendAllLines(CoinJoinIdStoreFilePath, new[] { id.ToString() });
+ return true;
}
}
catch (Exception ex)
@@ -37,29 +38,58 @@ public class CoinJoinIdStore : ICoinJoinIdStore
Logger.LogError($"Could not write to file {CoinJoinIdStoreFilePath}.", ex);
}
}
+ return false;
}
- public bool Contains(uint256 id)
+ public override bool Contains(uint256 id)
{
- return InMemoryCoinJoinIdStore.Contains(id);
+ return base.Contains(id);
}
- public static void ImportWW1CoinJoinsToWW2(string ww1CoinJoinsFilePath, string coinJoinIdStoreFilePath)
+ public static CoinJoinIdStore Create(string ww1CoinJoinsFilePath, string coinJoinIdStoreFilePath)
{
+ bool updateFile = false;
+ var coinjoins = Enumerable.Empty<string>();
+ if (!File.Exists(coinJoinIdStoreFilePath))
+ {
+ IoHelpers.EnsureContainingDirectoryExists(coinJoinIdStoreFilePath);
+ }
+ else
+ {
+ coinjoins = File.ReadAllLines(coinJoinIdStoreFilePath);
+ }
+
+ // Try to import ww1 coinjoins.
try
{
- var oldCoinjoins = File.ReadAllLines(ww1CoinJoinsFilePath);
+ var ww1Coinjoins = File.ReadAllLines(ww1CoinJoinsFilePath);
+
+ var missingWw1Coinjoins = ww1Coinjoins.Except(coinjoins);
- var newCoinjoins = File.ReadAllLines(coinJoinIdStoreFilePath);
- var missingOldCoinjoins = oldCoinjoins.Except(newCoinjoins);
- if (missingOldCoinjoins.Any())
+ if (missingWw1Coinjoins.Any())
{
- File.AppendAllLines(coinJoinIdStoreFilePath, missingOldCoinjoins);
+ coinjoins = missingWw1Coinjoins.Concat(coinjoins);
+ updateFile = true;
}
}
catch (Exception ex)
{
Logger.LogError("Failed to import WW1 coinjoins. Reason:", ex);
}
+
+ // Checking duplicates.
+ var distinctCoinJoins = coinjoins.Distinct();
+ if (distinctCoinJoins.Count() != coinjoins.Count())
+ {
+ coinjoins = distinctCoinJoins;
+ updateFile = true;
+ }
+
+ if (updateFile)
+ {
+ File.WriteAllLines(coinJoinIdStoreFilePath, coinjoins);
+ }
+
+ return new CoinJoinIdStore(coinjoins.Select(id => uint256.Parse(id)), coinJoinIdStoreFilePath);
}
}

MODIFY
WalletWasabi/WabiSabi/Backend/Rounds/CoinJoinStorage/ICoinJoinIdStore.cs
WalletWasabi/WabiSabi/Backend/Rounds/CoinJoinStorage/ICoinJoinIdStore.cs
@@ -4,7 +4,7 @@ namespace WalletWasabi.WabiSabi.Backend.Rounds.CoinJoinStorage;
public interface ICoinJoinIdStore
{
- public void Append(uint256 id);
+ public bool TryAdd(uint256 id);
public bool Contains(uint256 id);
}

MODIFY
WalletWasabi/WabiSabi/Backend/Rounds/CoinJoinStorage/InMemoryCoinJoinIdStore.cs
WalletWasabi/WabiSabi/Backend/Rounds/CoinJoinStorage/InMemoryCoinJoinIdStore.cs
@@ -7,11 +7,11 @@ using WalletWasabi.Logging;
namespace WalletWasabi.WabiSabi.Backend.Rounds.CoinJoinStorage;
-public class InMemoryCoinJoinIdStore
+public class InMemoryCoinJoinIdStore : ICoinJoinIdStore
{
public InMemoryCoinJoinIdStore(IEnumerable<uint256> coinJoinHashes)
{
- CoinJoinIds = new ConcurrentDictionary<uint256, byte>(coinJoinHashes.ToDictionary(x => x, x => byte.MinValue));
+ CoinJoinIds = new ConcurrentDictionary<uint256, byte>(coinJoinHashes.Distinct().ToDictionary(x => x, x => byte.MinValue));
}
public InMemoryCoinJoinIdStore() : this(Enumerable.Empty<uint256>())
@@ -21,24 +21,14 @@ public class InMemoryCoinJoinIdStore
// We would use a HashSet here but ConcurrentHashSet not exists.
private ConcurrentDictionary<uint256, byte> CoinJoinIds { get; }
- public bool Contains(uint256 hash)
+ public virtual bool Contains(uint256 hash)
{
return CoinJoinIds.ContainsKey(hash);
}
- public bool TryAdd(uint256 hash)
+ public virtual bool TryAdd(uint256 hash)
{
// The byte is just a dummy value, we are not using it.
return CoinJoinIds.TryAdd(hash, byte.MinValue);
}
-
- public static InMemoryCoinJoinIdStore LoadFromFile(string filePath)
- {
- var lines = File.Exists(filePath)
- ? File.ReadAllLines(filePath).Select(x => uint256.Parse(x))
- : Enumerable.Empty<uint256>();
-
- var store = new InMemoryCoinJoinIdStore(lines);
- return store;
- }
}

MODIFY
WalletWasabi/WabiSabi/WabiSabiCoordinator.cs
WalletWasabi/WabiSabi/WabiSabiCoordinator.cs
@@ -60,7 +60,7 @@ public class WabiSabiCoordinator : BackgroundService
private void Arena_CoinJoinBroadcast(object? sender, Transaction transaction)
{
- CoinJoinIdStore.Append(transaction.GetHash());
+ CoinJoinIdStore.TryAdd(transaction.GetHash());
var coinJoinScriptStoreFilePath = Parameters.CoinJoinScriptStoreFilePath;
try

Refactor CoinJoinIdStore to factory method