Make ccj client thread safe
MODIFY
WalletWasabi.Tests/RegTests.cs
WalletWasabi.Tests/RegTests.cs
@@ -1517,9 +1517,13 @@ namespace WalletWasabi.Tests
[Fact]
public async Task CcjCoordinatorCtorTestsAsync()
{
+ Logger.TurnOff(); // turn off at the end, otherwise, the tests logs would have of warnings
+
var rpc = Global.RpcClient;
var network = Global.RpcClient.Network;
+ await rpc.GenerateAsync(1);
+
var bestBlockHash = await rpc.GetBestBlockHashAsync();
var bestBlock = await rpc.GetBlockAsync(bestBlockHash);
var coinbaseTxId = bestBlock.Transactions[0].GetHash();
@@ -1557,6 +1561,8 @@ namespace WalletWasabi.Tests
Assert.Contains(coinbaseTxId.ToString(), txIds);
Assert.DoesNotContain(offchainTxId.ToString(), txIds);
Assert.DoesNotContain("This line is invalid (the file is corrupted)", txIds);
+
+ Logger.TurnOn();
}
[Fact]

MODIFY
WalletWasabi.Tests/SharedFixture.cs
WalletWasabi.Tests/SharedFixture.cs
@@ -32,7 +32,7 @@ namespace WalletWasabi.Tests
// Initialize tests...
Logger.SetFilePath(Path.Combine(DataDir, "Logs.txt"));
- Logger.SetMinimumLevel(LogLevel.Debug);
+ Logger.SetMinimumLevel(LogLevel.Info);
Logger.SetModes(LogMode.Debug, LogMode.Console, LogMode.File);
}

MODIFY
WalletWasabi/Logging/Logger.cs
WalletWasabi/Logging/Logger.cs
@@ -204,64 +204,19 @@ namespace WalletWasabi.Logging
#region ExceptionLoggingMethods
- /// <summary>
- /// Logs the <paramref name="ex"/>.Message at <paramref name="messageLevel"/>, if only <paramref name="messageLevel"/> logging is set.
- /// Logs the <paramref name="ex"/>.ToString() at <paramref name="messageLevel"/>, if <paramref name="toStringLevel"/> logging is also set.
- /// Note: this will never log at <paramref name="toStringLevel"/>.
- /// <paramref name="toStringLevel"/>Should be lower or equal to <paramref name="messageLevel"/>.
- /// </summary>
- private static void Log(Exception ex, LogLevel messageLevel, LogLevel toStringLevel = LogLevel.Trace, string category = "")
- {
- if (toStringLevel >= MinimumLevel)
- {
- Log(messageLevel, ex.ToString(), category);
- return;
- }
- if (messageLevel >= MinimumLevel)
+ private static void Log(Exception ex, LogLevel level, string category = "")
{
- Log(messageLevel, $"{ex.GetType()}: {ex.Message}", category);
- return;
- }
+ Log(level, ex.ToString(), category);
}
- /// <summary>
- /// Logs the <paramref name="ex"/>.Message at <paramref name="messageLevel"/>, if only <paramref name="messageLevel"/> logging is set.
- /// Logs the <paramref name="ex"/>.ToString() at <paramref name="messageLevel"/>, if <paramref name="toStringLevel"/> logging is also set.
- /// Note: this will never log at <paramref name="toStringLevel"/>.
- /// <paramref name="toStringLevel"/>Should be lower or equal to <paramref name="messageLevel"/>.
- /// </summary>
- private static void Log<T>(Exception ex, LogLevel messageLevel, LogLevel toStringLevel = LogLevel.Trace)
- {
- if (toStringLevel >= MinimumLevel)
+ private static void Log<T>(Exception ex, LogLevel level)
{
- Log<T>(messageLevel, ex.ToString());
- return;
- }
- if (messageLevel >= MinimumLevel)
- {
- Log<T>(messageLevel, $"{ex.GetType()}: {ex.Message}");
- return;
- }
+ Log<T>(level, ex.ToString());
}
- /// <summary>
- /// Logs the <paramref name="ex"/>.Message at <paramref name="messageLevel"/>, if only <paramref name="messageLevel"/> logging is set.
- /// Logs the <paramref name="ex"/>.ToString() at <paramref name="messageLevel"/>, if <paramref name="toStringLevel"/> logging is also set.
- /// Note: this will never log at <paramref name="toStringLevel"/>.
- /// <paramref name="toStringLevel"/>Should be lower or equal to <paramref name="messageLevel"/>.
- /// </summary>
- private static void Log(Exception ex, LogLevel messageLevel, LogLevel toStringLevel = LogLevel.Trace, Type category = null)
+ private static void Log(Exception ex, LogLevel level, Type category = null)
{
- if (toStringLevel >= MinimumLevel)
- {
- Log(messageLevel, ex.ToString(), category);
- return;
- }
- if (messageLevel >= MinimumLevel)
- {
- Log(messageLevel, $"{ex.GetType()}: {ex.Message}", category);
- return;
- }
+ Log(level, ex.ToString(), category);
}
#endregion
@@ -293,7 +248,7 @@ namespace WalletWasabi.Logging
/// These messages may contain sensitive application data and so should not be enabled in a production environment.
/// Example: "Credentials: {"User":"someuser", "Password":"P@ssword"}"
/// </summary>
- public static void LogTrace<T>(Exception ex) => Log<T>(ex, LogLevel.Trace, LogLevel.Trace);
+ public static void LogTrace<T>(Exception ex) => Log<T>(ex, LogLevel.Trace);
/// <summary>
/// Logs the <paramref name="ex"/>.ToString() at Trace level.
///
@@ -301,7 +256,7 @@ namespace WalletWasabi.Logging
/// These messages may contain sensitive application data and so should not be enabled in a production environment.
/// Example: "Credentials: {"User":"someuser", "Password":"P@ssword"}"
/// </summary>
- public static void LogTrace(Exception ex, Type category) => Log(ex, LogLevel.Trace, LogLevel.Trace, category);
+ public static void LogTrace(Exception ex, Type category) => Log(ex, LogLevel.Trace, category);
/// <summary>
/// Logs the <paramref name="ex"/>.ToString() at Trace level.
///
@@ -309,7 +264,7 @@ namespace WalletWasabi.Logging
/// These messages may contain sensitive application data and so should not be enabled in a production environment.
/// Example: "Credentials: {"User":"someuser", "Password":"P@ssword"}"
/// </summary>
- public static void LogTrace(Exception ex, string category = "") => Log(ex, LogLevel.Trace, LogLevel.Trace, category);
+ public static void LogTrace(Exception ex, string category = "") => Log(ex, LogLevel.Trace, category);
#endregion
@@ -344,7 +299,7 @@ namespace WalletWasabi.Logging
/// Example: "Credentials: {"User":"someuser", "Password":"P@ssword"}"
/// <paramref name="toStringLevel"/>Should be lower or equal to Debug level.
/// </summary>
- public static void LogDebug<T>(Exception ex, LogLevel toStringLevel = LogLevel.Trace) => Log<T>(ex, LogLevel.Debug, toStringLevel);
+ public static void LogDebug<T>(Exception ex, LogLevel toStringLevel = LogLevel.Trace) => Log<T>(ex, LogLevel.Debug);
/// <summary>
/// Logs the <paramref name="ex"/>.Message at Debug level, if only Debug level logging is set.
/// Logs the <paramref name="ex"/>.ToString() at Debug, if <paramref name="toStringLevel"/> logging is also set.
@@ -355,7 +310,7 @@ namespace WalletWasabi.Logging
/// Example: "Credentials: {"User":"someuser", "Password":"P@ssword"}"
/// <paramref name="toStringLevel"/>Should be lower or equal to Debug level.
/// </summary>
- public static void LogDebug(Exception ex, LogLevel toStringLevel = LogLevel.Trace, Type category = null) => Log(ex, LogLevel.Debug, toStringLevel, category);
+ public static void LogDebug(Exception ex, LogLevel toStringLevel = LogLevel.Trace, Type category = null) => Log(ex, LogLevel.Debug, category);
/// <summary>
/// Logs the <paramref name="ex"/>.Message at Debug level, if only Debug level logging is set.
/// Logs the <paramref name="ex"/>.ToString() at Debug, if <paramref name="toStringLevel"/> logging is also set.
@@ -366,7 +321,7 @@ namespace WalletWasabi.Logging
/// Example: "Credentials: {"User":"someuser", "Password":"P@ssword"}"
/// <paramref name="toStringLevel"/>Should be lower or equal to Debug level.
/// </summary>
- public static void LogDebug(Exception ex, LogLevel toStringLevel = LogLevel.Trace, string category = "") => Log(ex, LogLevel.Debug, toStringLevel, category);
+ public static void LogDebug(Exception ex, LogLevel toStringLevel = LogLevel.Trace, string category = "") => Log(ex, LogLevel.Debug, category);
#endregion
@@ -401,7 +356,7 @@ namespace WalletWasabi.Logging
/// Example: "Request received for path /api/my-controller"
/// <paramref name="toStringLevel"/>Should be lower or equal to Info level.
/// </summary>
- public static void LogInfo<T>(Exception ex, LogLevel toStringLevel = LogLevel.Trace) => Log<T>(ex, LogLevel.Info, toStringLevel);
+ public static void LogInfo<T>(Exception ex, LogLevel toStringLevel = LogLevel.Trace) => Log<T>(ex, LogLevel.Info);
/// <summary>
/// Logs the <paramref name="ex"/>.Message at Info level, if only Info level logging is set.
/// Logs the <paramref name="ex"/>.ToString() at Info, if <paramref name="toStringLevel"/> logging is also set.
@@ -412,7 +367,7 @@ namespace WalletWasabi.Logging
/// Example: "Request received for path /api/my-controller"
/// <paramref name="toStringLevel"/>Should be lower or equal to Info level.
/// </summary>
- public static void LogInfo(Exception ex, LogLevel toStringLevel = LogLevel.Trace, Type category = null) => Log(ex, LogLevel.Info, toStringLevel, category);
+ public static void LogInfo(Exception ex, LogLevel toStringLevel = LogLevel.Trace, Type category = null) => Log(ex, LogLevel.Info, category);
/// <summary>
/// Logs the <paramref name="ex"/>.Message at Info level, if only Info level logging is set.
/// Logs the <paramref name="ex"/>.ToString() at Info, if <paramref name="toStringLevel"/> logging is also set.
@@ -423,7 +378,7 @@ namespace WalletWasabi.Logging
/// Example: "Request received for path /api/my-controller"
/// <paramref name="toStringLevel"/>Should be lower or equal to Info level.
/// </summary>
- public static void LogInfo(Exception ex, LogLevel toStringLevel = LogLevel.Trace, string category = "") => Log(ex, LogLevel.Info, toStringLevel, category);
+ public static void LogInfo(Exception ex, LogLevel toStringLevel = LogLevel.Trace, string category = "") => Log(ex, LogLevel.Info, category);
#endregion
@@ -462,7 +417,7 @@ namespace WalletWasabi.Logging
/// Example: "FileNotFoundException for file quotes.txt."
/// <paramref name="toStringLevel"/>Should be lower or equal to Warning level.
/// </summary>
- public static void LogWarning<T>(Exception ex, LogLevel toStringLevel = LogLevel.Trace) => Log<T>(ex, LogLevel.Warning, toStringLevel);
+ public static void LogWarning<T>(Exception ex, LogLevel toStringLevel = LogLevel.Trace) => Log<T>(ex, LogLevel.Warning);
/// <summary>
/// Logs the <paramref name="ex"/>.Message at Warning level, if only Warning level logging is set.
/// Logs the <paramref name="ex"/>.ToString() at Warning, if <paramref name="toStringLevel"/> logging is also set.
@@ -474,7 +429,7 @@ namespace WalletWasabi.Logging
/// Example: "FileNotFoundException for file quotes.txt."
/// <paramref name="toStringLevel"/>Should be lower or equal to Warning level.
/// </summary>
- public static void LogWarning(Exception ex, LogLevel toStringLevel = LogLevel.Trace, Type category = null) => Log(ex, LogLevel.Warning, toStringLevel, category);
+ public static void LogWarning(Exception ex, LogLevel toStringLevel = LogLevel.Trace, Type category = null) => Log(ex, LogLevel.Warning, category);
/// <summary>
/// Logs the <paramref name="ex"/>.Message at Warning level, if only Warning level logging is set.
/// Logs the <paramref name="ex"/>.ToString() at Warning, if <paramref name="toStringLevel"/> logging is also set.
@@ -486,7 +441,7 @@ namespace WalletWasabi.Logging
/// Example: "FileNotFoundException for file quotes.txt."
/// <paramref name="toStringLevel"/>Should be lower or equal to Warning level.
/// </summary>
- public static void LogWarning(Exception ex, LogLevel toStringLevel = LogLevel.Trace, string category = "") => Log(ex, LogLevel.Warning, toStringLevel, category);
+ public static void LogWarning(Exception ex, LogLevel toStringLevel = LogLevel.Trace, string category = "") => Log(ex, LogLevel.Warning, category);
#endregion
@@ -521,7 +476,7 @@ namespace WalletWasabi.Logging
/// Example log message: "Cannot insert record due to duplicate key violation."
/// <paramref name="toStringLevel"/>Should be lower or equal to Error level.
/// </summary>
- public static void LogError<T>(Exception ex, LogLevel toStringLevel = LogLevel.Trace) => Log<T>(ex, LogLevel.Error, toStringLevel);
+ public static void LogError<T>(Exception ex, LogLevel toStringLevel = LogLevel.Trace) => Log<T>(ex, LogLevel.Error);
/// <summary>
/// Logs the <paramref name="ex"/>.Message at Error level, if only Error level logging is set.
/// Logs the <paramref name="ex"/>.ToString() at Error, if <paramref name="toStringLevel"/> logging is also set.
@@ -532,7 +487,7 @@ namespace WalletWasabi.Logging
/// Example log message: "Cannot insert record due to duplicate key violation."
/// <paramref name="toStringLevel"/>Should be lower or equal to Error level.
/// </summary>
- public static void LogError(Exception ex, LogLevel toStringLevel = LogLevel.Trace, Type category = null) => Log(ex, LogLevel.Error, toStringLevel, category);
+ public static void LogError(Exception ex, LogLevel toStringLevel = LogLevel.Trace, Type category = null) => Log(ex, LogLevel.Error, category);
/// <summary>
/// Logs the <paramref name="ex"/>.Message at Error level, if only Error level logging is set.
/// Logs the <paramref name="ex"/>.ToString() at Error, if <paramref name="toStringLevel"/> logging is also set.
@@ -543,7 +498,7 @@ namespace WalletWasabi.Logging
/// Example log message: "Cannot insert record due to duplicate key violation."
/// <paramref name="toStringLevel"/>Should be lower or equal to Error level.
/// </summary>
- public static void LogError(Exception ex, LogLevel toStringLevel = LogLevel.Trace, string category = "") => Log(ex, LogLevel.Error, toStringLevel, category);
+ public static void LogError(Exception ex, LogLevel toStringLevel = LogLevel.Trace, string category = "") => Log(ex, LogLevel.Error, category);
#endregion
@@ -574,7 +529,7 @@ namespace WalletWasabi.Logging
/// Examples: data loss scenarios, out of disk space.
/// <paramref name="toStringLevel"/>Should be lower or equal to Critical level.
/// </summary>
- public static void LogCritical<T>(Exception ex, LogLevel toStringLevel = LogLevel.Trace) => Log<T>(ex, LogLevel.Critical, toStringLevel);
+ public static void LogCritical<T>(Exception ex, LogLevel toStringLevel = LogLevel.Trace) => Log<T>(ex, LogLevel.Critical);
/// <summary>
/// Logs the <paramref name="ex"/>.Message at Critical level, if only Critical level logging is set.
/// Logs the <paramref name="ex"/>.ToString() at Critical, if <paramref name="toStringLevel"/> logging is also set.
@@ -584,7 +539,7 @@ namespace WalletWasabi.Logging
/// Examples: data loss scenarios, out of disk space.
/// <paramref name="toStringLevel"/>Should be lower or equal to Critical level.
/// </summary>
- public static void LogCritical(Exception ex, LogLevel toStringLevel = LogLevel.Trace, Type category = null) => Log(ex, LogLevel.Critical, toStringLevel, category);
+ public static void LogCritical(Exception ex, LogLevel toStringLevel = LogLevel.Trace, Type category = null) => Log(ex, LogLevel.Critical, category);
/// <summary>
/// Logs the <paramref name="ex"/>.Message at Critical level, if only Critical level logging is set.
/// Logs the <paramref name="ex"/>.ToString() at Critical, if <paramref name="toStringLevel"/> logging is also set.
@@ -594,7 +549,7 @@ namespace WalletWasabi.Logging
/// Examples: data loss scenarios, out of disk space.
/// <paramref name="toStringLevel"/>Should be lower or equal to Critical level.
/// </summary>
- public static void LogCritical(Exception ex, LogLevel toStringLevel = LogLevel.Trace, string category = "") => Log(ex, LogLevel.Critical, toStringLevel, category);
+ public static void LogCritical(Exception ex, LogLevel toStringLevel = LogLevel.Trace, string category = "") => Log(ex, LogLevel.Critical, category);
#endregion

MODIFY
WalletWasabi/Models/ChaumianCoinJoin/CcjClientState.cs
WalletWasabi/Models/ChaumianCoinJoin/CcjClientState.cs
@@ -69,15 +69,23 @@ namespace WalletWasabi.Models.ChaumianCoinJoin
}
}
- public IEnumerable<SmartCoin> GetAllCoins()
+ public SmartCoin GetSingleOrDefaultFromWaitingList((uint256 txid, int index) coinReference)
{
lock (StateLock)
{
- return WaitingList.Concat(Rounds.SelectMany(x => x.CoinsRegistered)).ToArray();
+ return WaitingList.SingleOrDefault(x => x.TransactionId == coinReference.txid && x.Index == coinReference.index);
}
}
- public IEnumerable<SmartCoin> GetRegistrableCoins(int maximumInputCountPerPeer, Money denomination, Money feePerInputs, Money feePerOutputs)
+ public IEnumerable<(uint256 txid, int index)> GetAllCoins()
+ {
+ lock (StateLock)
+ {
+ return WaitingList.Concat(Rounds.SelectMany(x => x.CoinsRegistered)).Select(x=>(x.TransactionId, x.Index)).ToArray();
+ }
+ }
+
+ public IEnumerable<(uint256 txid, int index)> GetRegistrableCoins(int maximumInputCountPerPeer, Money denomination, Money feePerInputs, Money feePerOutputs)
{
lock (StateLock)
{
@@ -93,34 +101,34 @@ namespace WalletWasabi.Models.ChaumianCoinJoin
if (maximumInputCountPerPeer < coinsToRegister.Count)
{
- return Enumerable.Empty<SmartCoin>(); // Inputs are too small, max input to be registered is reached.
+ return Enumerable.Empty<(uint256 txid, int index)>(); // Inputs are too small, max input to be registered is reached.
}
amountSoFar += coin.Amount;
if (amountSoFar > amountNeededExceptInputFees + feePerInputs * coinsToRegister.Count)
{
// If input count doesn't reach the max input registration AND there are enough coins queued, then can register to mix.
- return coinsToRegister;
+ return coinsToRegister.Select(x => (x.TransactionId, x.Index)).ToArray();
}
}
- return Enumerable.Empty<SmartCoin>(); // Amount is never reached.
+ return Enumerable.Empty<(uint256 txid, int index)>(); // Amount is never reached.
}
}
- public IEnumerable<CcjClientRound> GetActivelyMixingRounds()
+ public IEnumerable<long> GetActivelyMixingRounds()
{
lock (StateLock)
{
- return Rounds.Where(x => x.AliceClient != null && x.State.Phase >= CcjRoundPhase.ConnectionConfirmation).ToArray();
+ return Rounds.Where(x => x.AliceClient != null && x.State.Phase >= CcjRoundPhase.ConnectionConfirmation).Select(x=>x.State.RoundId).ToArray();
}
}
- public IEnumerable<CcjClientRound> GetPassivelyMixingRounds()
+ public IEnumerable<long> GetPassivelyMixingRounds()
{
lock (StateLock)
{
- return Rounds.Where(x => x.AliceClient != null && x.State.Phase == CcjRoundPhase.InputRegistration).ToArray();
+ return Rounds.Where(x => x.AliceClient != null && x.State.Phase == CcjRoundPhase.InputRegistration).Select(x => x.State.RoundId).ToArray();
}
}
@@ -173,15 +181,14 @@ namespace WalletWasabi.Models.ChaumianCoinJoin
}
}
- public CcjClientRound GetRegistrableRound()
+ public CcjClientRound GetRegistrableRoundOrDefault()
{
lock(StateLock)
{
- return Rounds.First(x => x.State.Phase == CcjRoundPhase.InputRegistration);
+ return Rounds.FirstOrDefault(x => x.State.Phase == CcjRoundPhase.InputRegistration);
}
}
-
public void AddOrReplaceRound(CcjClientRound round)
{
lock (StateLock)

MODIFY
WalletWasabi/Services/CcjClient.cs
WalletWasabi/Services/CcjClient.cs
@@ -152,12 +152,14 @@ namespace WalletWasabi.Services
{
State.RemoveSpentCoinsFromWaitingList(); // Make sure coins those were somehow spent are removed.
- CcjClientRound inputRegistrableRound = State.GetRegistrableRound();
+ CcjClientRound inputRegistrableRound = State.GetRegistrableRoundOrDefault();
+ if (inputRegistrableRound != null)
+ {
if (inputRegistrableRound.AliceClient == null) // If didn't register already, check what can we register.
{
try
{
- IEnumerable<SmartCoin> registrableCoins = State.GetRegistrableCoins(
+ IEnumerable<(uint256 txid, int index)> registrableCoins = State.GetRegistrableCoins(
inputRegistrableRound.State.MaximumInputCountPerPeer,
inputRegistrableRound.State.Denomination,
inputRegistrableRound.State.FeePerInputs,
@@ -170,8 +172,10 @@ namespace WalletWasabi.Services
var blind = CoordinatorPubKey.Blind(activeKey.GetP2wpkhScript().ToBytes());
var inputProofs = new List<InputProofModel>();
- foreach (SmartCoin coin in registrableCoins)
+ foreach ((uint256 txid, int index) coinReference in registrableCoins)
{
+ var coin = State.GetSingleOrDefaultFromWaitingList(coinReference);
+ if (coin == null) throw new NotSupportedException("This is impossible.");
var inputProof = new InputProofModel
{
Input = coin.GetOutPoint(),
@@ -197,8 +201,10 @@ namespace WalletWasabi.Services
State.AddOrReplaceRound(roundRegistered);
}
- foreach (var coin in registrableCoins)
+ foreach ((uint256 txid, int index) coinReference in registrableCoins)
{
+ var coin = State.GetSingleOrDefaultFromWaitingList(coinReference);
+ if (coin == null) throw new NotSupportedException("This is impossible.");
roundRegistered.CoinsRegistered.Add(coin);
State.RemoveCoinFromWaitingList(coin);
}
@@ -233,11 +239,15 @@ namespace WalletWasabi.Services
Logger.LogError<CcjClient>(ex);
}
}
+ }
- foreach (CcjClientRound ongoingRound in State.GetActivelyMixingRounds())
+ foreach (int ongoingRoundId in State.GetActivelyMixingRounds())
{
try
{
+ var ongoingRound = State.GetSingleOrDefaultRound(ongoingRoundId);
+ if (ongoingRound == null) throw new NotSupportedException("This is impossible.");
+
if (ongoingRound.State.Phase == CcjRoundPhase.ConnectionConfirmation)
{
if (ongoingRound.RoundHash == null) // If we didn't already obtained our roundHash obtain it.
@@ -319,7 +329,7 @@ namespace WalletWasabi.Services
{
if (ex.Message.StartsWith("NotFound", StringComparison.Ordinal)) // Alice timed out.
{
- State.ClearRoundRegistration(ongoingRound.State.RoundId);
+ State.ClearRoundRegistration(ongoingRoundId);
}
Logger.LogError<CcjClient>(ex);
}
@@ -385,8 +395,11 @@ namespace WalletWasabi.Services
foreach (var coinToDequeue in coins)
{
- foreach (var round in State.GetPassivelyMixingRounds())
+ foreach (int roundId in State.GetPassivelyMixingRounds())
{
+ var round = State.GetSingleOrDefaultRound(roundId);
+ if (round == null) throw new NotSupportedException("This is impossible.");
+
if (round.CoinsRegistered.Contains(coinToDequeue))
{
try
@@ -401,8 +414,11 @@ namespace WalletWasabi.Services
}
}
- foreach (var round in State.GetActivelyMixingRounds())
+ foreach (int roundId in State.GetActivelyMixingRounds())
{
+ var round = State.GetSingleOrDefaultRound(roundId);
+ if (round == null) throw new NotSupportedException("This is impossible.");
+
if (round.CoinsRegistered.Contains(coinToDequeue))
{
exceptions.Add(new NotSupportedException($"Cannot deque coin in {round.State.Phase} phase. Coin: {coinToDequeue.Index}:{coinToDequeue.TransactionId}."));
@@ -445,9 +461,13 @@ namespace WalletWasabi.Services
SatoshiClient?.Dispose();
State.DisposeAllAliceClients();
+ foreach (var coinReference in State.GetAllCoins())
+ {
try
{
- await DequeueCoinsFromMixAsync(State.GetAllCoins().ToArray());
+ var coin = State.GetSingleOrDefaultFromWaitingList(coinReference);
+ if (coin == null) throw new NotSupportedException("This is impossible.");
+ await DequeueCoinsFromMixAsync(coin);
}
catch (Exception ex)
{
@@ -468,3 +488,4 @@ namespace WalletWasabi.Services
}
}
}
+}

Make ccj client thread safe