Improve dequeue messages
MODIFY
WalletWasabi.Gui/CommandLine/Daemon.cs
WalletWasabi.Gui/CommandLine/Daemon.cs
@@ -5,6 +5,7 @@ using System.Linq;
using System.Text;
using System.Threading.Tasks;
using WalletWasabi.Blockchain.Keys;
+using WalletWasabi.CoinJoin.Client.Clients.Queuing;
using WalletWasabi.Helpers;
using WalletWasabi.Logging;
@@ -115,7 +116,7 @@ namespace WalletWasabi.Gui.CommandLine
if (!Global.KillRequested) // This only has to run if it finishes by itself. Otherwise the Ctrl+c runs it.
{
- await Global.ChaumianClient?.DequeueAllCoinsFromMixAsync("Stopping Wasabi.");
+ await Global.ChaumianClient?.DequeueAllCoinsFromMixAsync(DequeueReason.ApplicationExit);
}
}
catch

MODIFY
WalletWasabi.Gui/Controls/WalletExplorer/CoinJoinTabViewModel.cs
WalletWasabi.Gui/Controls/WalletExplorer/CoinJoinTabViewModel.cs
@@ -20,6 +20,7 @@ using WalletWasabi.CoinJoin.Common.Models;
using WalletWasabi.CoinJoin.Client.Rounds;
using WalletWasabi.Gui.Helpers;
using System.Security;
+using WalletWasabi.CoinJoin.Client.Clients.Queuing;
namespace WalletWasabi.Gui.Controls.WalletExplorer
{
@@ -129,7 +130,7 @@ namespace WalletWasabi.Gui.Controls.WalletExplorer
CoordinatorFeePercent = registrableRound?.State?.CoordinatorFeePercent.ToString() ?? "0.003";
Observable.FromEventPattern(Global.ChaumianClient, nameof(Global.ChaumianClient.CoinQueued))
- .Merge(Observable.FromEventPattern(Global.ChaumianClient, nameof(Global.ChaumianClient.CoinDequeued)))
+ .Merge(Observable.FromEventPattern(Global.ChaumianClient, nameof(Global.ChaumianClient.OnDequeued)))
.Merge(Observable.FromEventPattern(Global.ChaumianClient, nameof(Global.ChaumianClient.StateUpdated)))
.ObserveOn(RxApp.MainThreadScheduler)
.Subscribe(_ => UpdateStates())
@@ -192,7 +193,7 @@ namespace WalletWasabi.Gui.Controls.WalletExplorer
try
{
- await Global.ChaumianClient.DequeueCoinsFromMixAsync(selectedCoins.Select(c => c.Model).ToArray(), "Dequeued by the user.");
+ await Global.ChaumianClient.DequeueCoinsFromMixAsync(selectedCoins.Select(c => c.Model).ToArray(), DequeueReason.UserRequested);
}
catch (Exception ex)
{

MODIFY
WalletWasabi.Gui/Controls/WalletExplorer/CoinViewModel.cs
WalletWasabi.Gui/Controls/WalletExplorer/CoinViewModel.cs
@@ -166,10 +166,7 @@ namespace WalletWasabi.Gui.Controls.WalletExplorer
if (Model.CoinJoinInProgress && Global.ChaumianClient != null)
{
ClientState clientState = Global.ChaumianClient.State;
- foreach (long roundId in clientState.GetAllMixingRounds())
- {
- ClientRound round = clientState.GetSingleOrDefaultRound(roundId);
- if (round != default)
+ foreach (var round in clientState.GetAllMixingRounds())
{
if (round.CoinsRegistered.Contains(Model))
{
@@ -192,7 +189,6 @@ namespace WalletWasabi.Gui.Controls.WalletExplorer
}
}
}
- }
if (Model.SpentAccordingToBackend)
{

MODIFY
WalletWasabi.Gui/Controls/WalletExplorer/SendTabViewModel.cs
WalletWasabi.Gui/Controls/WalletExplorer/SendTabViewModel.cs
@@ -22,6 +22,7 @@ using WalletWasabi.Blockchain.Analysis.FeesEstimation;
using WalletWasabi.Blockchain.TransactionBuilding;
using WalletWasabi.Blockchain.TransactionOutputs;
using WalletWasabi.Blockchain.Transactions;
+using WalletWasabi.CoinJoin.Client.Clients.Queuing;
using WalletWasabi.Exceptions;
using WalletWasabi.Gui.Helpers;
using WalletWasabi.Gui.Models;
@@ -293,9 +294,7 @@ namespace WalletWasabi.Gui.Controls.WalletExplorer
TxoRef[] toDequeue = selectedCoinViewModels.Where(x => x.CoinJoinInProgress).Select(x => x.Model.GetTxoRef()).ToArray();
if (toDequeue != null && toDequeue.Any())
{
- var reason = "Coins dequeued, because of normal send.";
- await Global.ChaumianClient.DequeueCoinsFromMixAsync(toDequeue, reason);
- NotificationHelpers.Warning(reason, "");
+ await Global.ChaumianClient.DequeueCoinsFromMixAsync(toDequeue, DequeueReason.TransactionBuilding);
}
}
catch

MODIFY
WalletWasabi.Gui/Dialogs/CannotCloseDialogViewModel.cs
WalletWasabi.Gui/Dialogs/CannotCloseDialogViewModel.cs
@@ -8,6 +8,7 @@ using System.Reactive.Disposables;
using System.Threading;
using System.Threading.Tasks;
using WalletWasabi.Blockchain.TransactionOutputs;
+using WalletWasabi.CoinJoin.Client.Clients.Queuing;
using WalletWasabi.Logging;
namespace WalletWasabi.Gui.Dialogs
@@ -147,7 +148,7 @@ namespace WalletWasabi.Gui.Dialogs
break;
}
- await Global.ChaumianClient.DequeueCoinsFromMixAsync(new SmartCoin[] { coin }, "Closing Wasabi."); // Dequeue coins one-by-one to check cancel flag more frequently.
+ await Global.ChaumianClient.DequeueCoinsFromMixAsync(new SmartCoin[] { coin }, DequeueReason.ApplicationExit); // Dequeue coins one-by-one to check cancel flag more frequently.
}
catch (Exception ex)
{

MODIFY
WalletWasabi.Gui/Global.cs
WalletWasabi.Gui/Global.cs
@@ -26,6 +26,7 @@ using WalletWasabi.Blockchain.TransactionOutputs;
using WalletWasabi.Blockchain.TransactionProcessing;
using WalletWasabi.CoinJoin.Client;
using WalletWasabi.CoinJoin.Client.Clients;
+using WalletWasabi.CoinJoin.Client.Clients.Queuing;
using WalletWasabi.CoinJoin.Client.Rounds;
using WalletWasabi.Gui.Helpers;
using WalletWasabi.Helpers;
@@ -134,7 +135,7 @@ namespace WalletWasabi.Gui
if (enqueuedCoins.Any())
{
Logger.LogWarning("Unregistering coins in CoinJoin process.");
- await ChaumianClient.DequeueCoinsFromMixAsync(enqueuedCoins, "Process was signaled to kill.");
+ await ChaumianClient.DequeueCoinsFromMixAsync(enqueuedCoins, DequeueReason.ApplicationExit);
}
}
@@ -483,21 +484,59 @@ namespace WalletWasabi.Gui
CoinJoinProcessor.AddWalletService(WalletService);
WalletService.TransactionProcessor.WalletRelevantTransactionProcessed += TransactionProcessor_WalletRelevantTransactionProcessed;
- ChaumianClient.CoinDequeued += ChaumianClient_CoinDequeued;
+ ChaumianClient.OnDequeued += ChaumianClient_OnDequeued;
}
_cancelWalletServiceInitialization = null; // Must make it null explicitly, because dispose won't make it null.
}
- private void ChaumianClient_CoinDequeued(object sender, DequeueCoin coin)
+ private void ChaumianClient_OnDequeued(object sender, DequeueResult e)
{
try
{
- if (UiConfig?.LurkingWifeMode is true || string.IsNullOrWhiteSpace(coin.Reason))
+ if (UiConfig?.LurkingWifeMode is true)
{
return;
}
- NotificationHelpers.Information(coin.Reason, $"Coin ({coin.Coin.Amount.ToString(false, true)}) Dequeued");
+ foreach (var success in e.Successful.Where(x => x.Value.Any()))
+ {
+ DequeueReason reason = success.Key;
+ if (success.Value.Count() == 1)
+ {
+ var single = success.Value.First();
+ NotificationHelpers.Information(reason.ToString(), $"Coin ({single.Amount.ToString(false, true)}) Dequeued");
+ }
+ else
+ {
+ if (reason != DequeueReason.Spent)
+ {
+ var message = reason.ToString();
+ var title = $"{success.Value.Count()} Coins Dequeued";
+ if (reason == DequeueReason.UserRequested)
+ {
+ NotificationHelpers.Information("", title);
+ }
+ else
+ {
+ NotificationHelpers.Warning(message, title);
+ }
+ }
+ }
+ }
+
+ foreach (var failure in e.Unsuccessful.Where(x => x.Value.Any()))
+ {
+ DequeueReason reason = failure.Key;
+ if (failure.Value.Count() == 1)
+ {
+ var single = failure.Value.First();
+ NotificationHelpers.Warning(reason.ToString(), $"Couldn't Dequeue Coin ({single.Amount.ToString(false, true)})");
+ }
+ else
+ {
+ NotificationHelpers.Warning(reason.ToString(), $"Couldn't Dequeue {failure.Value.Count()} Coins");
+ }
+ }
}
catch (Exception ex)
{
@@ -674,7 +713,7 @@ namespace WalletWasabi.Gui
if (walletService is { })
{
WalletService.TransactionProcessor.WalletRelevantTransactionProcessed -= TransactionProcessor_WalletRelevantTransactionProcessed;
- ChaumianClient.CoinDequeued -= ChaumianClient_CoinDequeued;
+ ChaumianClient.OnDequeued -= ChaumianClient_OnDequeued;
}
try

MODIFY
WalletWasabi.Tests/IntegrationTests/RegTests.cs
WalletWasabi.Tests/IntegrationTests/RegTests.cs
@@ -30,6 +30,7 @@ using WalletWasabi.Blockchain.TransactionOutputs;
using WalletWasabi.Blockchain.TransactionProcessing;
using WalletWasabi.Blockchain.Transactions;
using WalletWasabi.CoinJoin.Client.Clients;
+using WalletWasabi.CoinJoin.Client.Clients.Queuing;
using WalletWasabi.CoinJoin.Client.Rounds;
using WalletWasabi.CoinJoin.Common.Models;
using WalletWasabi.CoinJoin.Coordinator;
@@ -3165,7 +3166,7 @@ namespace WalletWasabi.Tests.IntegrationTests
if (chaumianClient != null)
{
- await chaumianClient.DequeueAllCoinsFromMixAsync("");
+ await chaumianClient.DequeueAllCoinsFromMixAsync(DequeueReason.UserRequested);
await chaumianClient.StopAsync();
}
}
@@ -3246,19 +3247,19 @@ namespace WalletWasabi.Tests.IntegrationTests
Assert.True(smartCoin2.CoinJoinInProgress);
// Make sure it does not throw.
- await chaumianClient1.DequeueCoinsFromMixAsync(new SmartCoin((network.Consensus.ConsensusFactory.CreateTransaction()).GetHash(), 1, new Script(), Money.Parse("3"), new TxoRef[] { new TxoRef((network.Consensus.ConsensusFactory.CreateTransaction()).GetHash(), 0) }, Height.Mempool, replaceable: false, anonymitySet: 1, isLikelyCoinJoinOutput: false), "");
+ await chaumianClient1.DequeueCoinsFromMixAsync(new SmartCoin((network.Consensus.ConsensusFactory.CreateTransaction()).GetHash(), 1, new Script(), Money.Parse("3"), new TxoRef[] { new TxoRef((network.Consensus.ConsensusFactory.CreateTransaction()).GetHash(), 0) }, Height.Mempool, replaceable: false, anonymitySet: 1, isLikelyCoinJoinOutput: false), DequeueReason.UserRequested);
Assert.True(2 == (await chaumianClient1.QueueCoinsToMixAsync(password, smartCoin1, smartCoin2)).Count());
- await chaumianClient1.DequeueCoinsFromMixAsync(smartCoin1, "");
+ await chaumianClient1.DequeueCoinsFromMixAsync(smartCoin1, DequeueReason.UserRequested);
Assert.False(smartCoin1.CoinJoinInProgress);
- await chaumianClient1.DequeueCoinsFromMixAsync(new[] { smartCoin1, smartCoin2 }, "");
+ await chaumianClient1.DequeueCoinsFromMixAsync(new[] { smartCoin1, smartCoin2 }, DequeueReason.UserRequested);
Assert.False(smartCoin1.CoinJoinInProgress);
Assert.False(smartCoin2.CoinJoinInProgress);
Assert.True(2 == (await chaumianClient1.QueueCoinsToMixAsync(password, smartCoin1, smartCoin2)).Count());
Assert.True(smartCoin1.CoinJoinInProgress);
Assert.True(smartCoin2.CoinJoinInProgress);
- await chaumianClient1.DequeueCoinsFromMixAsync(smartCoin1, "");
- await chaumianClient1.DequeueCoinsFromMixAsync(smartCoin2, "");
+ await chaumianClient1.DequeueCoinsFromMixAsync(smartCoin1, DequeueReason.UserRequested);
+ await chaumianClient1.DequeueCoinsFromMixAsync(smartCoin2, DequeueReason.UserRequested);
Assert.False(smartCoin1.CoinJoinInProgress);
Assert.False(smartCoin2.CoinJoinInProgress);
@@ -3288,7 +3289,7 @@ namespace WalletWasabi.Tests.IntegrationTests
await coordinator.RoundConfig.UpdateOrDefaultAsync(roundConfig, toFile: true);
coordinator.AbortAllRoundsInInputRegistration("");
Assert.NotEmpty(chaumianClient1.State.GetAllQueuedCoins());
- await chaumianClient1.DequeueAllCoinsFromMixAsync("");
+ await chaumianClient1.DequeueAllCoinsFromMixAsync(DequeueReason.UserRequested);
Assert.Empty(chaumianClient1.State.GetAllQueuedCoins());
await chaumianClient1.QueueCoinsToMixAsync(password, smartCoin4);
Assert.NotEmpty(chaumianClient1.State.GetAllQueuedCoins());
@@ -3469,8 +3470,8 @@ namespace WalletWasabi.Tests.IntegrationTests
{
try
{
- await chaumianClient.DequeueAllCoinsFromMixAsync("");
- await chaumianClient2.DequeueAllCoinsFromMixAsync("");
+ await chaumianClient.DequeueAllCoinsFromMixAsync(DequeueReason.UserRequested);
+ await chaumianClient2.DequeueAllCoinsFromMixAsync(DequeueReason.UserRequested);
break;
}
catch (NotSupportedException)

MODIFY
WalletWasabi/CoinJoin/Client/Clients/CoinJoinClient.cs
WalletWasabi/CoinJoin/Client/Clients/CoinJoinClient.cs
@@ -5,6 +5,7 @@ using Nito.AsyncEx;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
+using System.Collections.Immutable;
using System.ComponentModel;
using System.Linq;
using System.Net;
@@ -15,6 +16,7 @@ using WalletWasabi.Backend.Models.Responses;
using WalletWasabi.Blockchain.Analysis.Clustering;
using WalletWasabi.Blockchain.Keys;
using WalletWasabi.Blockchain.TransactionOutputs;
+using WalletWasabi.CoinJoin.Client.Clients.Queuing;
using WalletWasabi.CoinJoin.Client.Rounds;
using WalletWasabi.CoinJoin.Common.Crypto;
using WalletWasabi.CoinJoin.Common.Models;
@@ -50,7 +52,7 @@ namespace WalletWasabi.CoinJoin.Client.Clients
public event EventHandler<SmartCoin> CoinQueued;
- public event EventHandler<DequeueCoin> CoinDequeued;
+ public event EventHandler<DequeueResult> OnDequeued;
private long _frequentStatusProcessingIfNotMixing;
@@ -225,19 +227,22 @@ namespace WalletWasabi.CoinJoin.Client.Clients
// If we do not have enough coin queued to register a round, then dequeue all.
ClientRound registrableRound = State.GetRegistrableRoundOrDefault();
- if (registrableRound != default)
+ if (registrableRound is { })
{
+ DequeueReason? reason = null;
// If the coordinator increases fees, do not register. Let the users register manually again.
- bool dequeueBecauseCoordinatorFeeChanged = false;
- if (CoordinatorFeepercentToCheck != default)
+ if (CoordinatorFeepercentToCheck is { } && registrableRound.State.CoordinatorFeePercent > CoordinatorFeepercentToCheck)
{
- dequeueBecauseCoordinatorFeeChanged = registrableRound.State.CoordinatorFeePercent > CoordinatorFeepercentToCheck;
+ reason = DequeueReason.CoordinatorFeeChanged;
+ }
+ else if (!registrableRound.State.HaveEnoughQueued(State.GetAllQueuedCoinAmounts()))
+ {
+ reason = DequeueReason.NotEnoughFundsEnqueued;
}
- if (!registrableRound.State.HaveEnoughQueued(State.GetAllQueuedCoinAmounts().ToArray())
- || dequeueBecauseCoordinatorFeeChanged)
+ if (reason.HasValue)
{
- await DequeueAllCoinsFromMixNoLockAsync("The total value of the registered coins is not enough or the coordinator's fee changed.").ConfigureAwait(false);
+ await DequeueAllCoinsFromMixNoLockAsync(reason.Value).ConfigureAwait(false);
}
}
}
@@ -254,9 +259,9 @@ namespace WalletWasabi.CoinJoin.Client.Clients
using (await MixLock.LockAsync().ConfigureAwait(false))
{
- foreach (long ongoingRoundId in State.GetActivelyMixingRounds())
+ foreach (var ongoingRound in State.GetActivelyMixingRounds())
{
- await TryProcessRoundStateAsync(ongoingRoundId).ConfigureAwait(false);
+ await TryProcessRoundStateAsync(ongoingRound).ConfigureAwait(false);
}
await DequeueSpentCoinsFromMixNoLockAsync().ConfigureAwait(false);
@@ -289,16 +294,10 @@ namespace WalletWasabi.CoinJoin.Client.Clients
}
}
- private async Task TryProcessRoundStateAsync(long ongoingRoundId)
+ private async Task TryProcessRoundStateAsync(ClientRound ongoingRound)
{
try
{
- var ongoingRound = State.GetSingleOrDefaultRound(ongoingRoundId);
- if (ongoingRound is null)
- {
- throw new NotSupportedException("This is impossible.");
- }
-
if (ongoingRound.State.Phase == RoundPhase.ConnectionConfirmation)
{
if (!ongoingRound.Registration.IsPhaseActionsComleted(RoundPhase.ConnectionConfirmation)) // If we did not already confirm connection in connection confirmation phase confirm it.
@@ -335,7 +334,7 @@ namespace WalletWasabi.CoinJoin.Client.Clients
Logger.LogError(ex); // Keep this in front of the logic (Logs will make more sense.)
if (ex.Message.StartsWith("Not Found", StringComparison.Ordinal)) // Alice timed out.
{
- State.ClearRoundRegistration(ongoingRoundId);
+ State.ClearRoundRegistration(ongoingRound.RoundId);
}
}
}
@@ -566,7 +565,7 @@ namespace WalletWasabi.CoinJoin.Client.Clients
Logger.LogWarning(ex.Message.Split('\n')[1]);
- await DequeueCoinsFromMixNoLockAsync(coinReference, "Failed to register the coin with the coordinator.").ConfigureAwait(false);
+ await DequeueCoinsFromMixNoLockAsync(coinReference, DequeueReason.Banned).ConfigureAwait(false);
aliceClient?.Dispose();
return;
}
@@ -586,7 +585,7 @@ namespace WalletWasabi.CoinJoin.Client.Clients
Logger.LogWarning(ex.Message.Split('\n')[1]);
- await DequeueCoinsFromMixNoLockAsync(coinReference, "Failed to register the coin with the coordinator. The coin is already spent.").ConfigureAwait(false);
+ await DequeueCoinsFromMixNoLockAsync(coinReference, DequeueReason.Spent).ConfigureAwait(false);
aliceClient?.Dispose();
return;
}
@@ -820,12 +819,12 @@ namespace WalletWasabi.CoinJoin.Client.Clients
return successful;
}
- public async Task DequeueCoinsFromMixAsync(SmartCoin coin, string reason)
+ public async Task DequeueCoinsFromMixAsync(SmartCoin coin, DequeueReason reason)
{
await DequeueCoinsFromMixAsync(new[] { coin }, reason).ConfigureAwait(false);
}
- public async Task DequeueCoinsFromMixAsync(IEnumerable<SmartCoin> coins, string reason)
+ public async Task DequeueCoinsFromMixAsync(IEnumerable<SmartCoin> coins, DequeueReason reason)
{
if (coins is null || !coins.Any())
{
@@ -881,12 +880,12 @@ namespace WalletWasabi.CoinJoin.Client.Clients
}
}
- public async Task DequeueCoinsFromMixAsync(TxoRef coin, string reason)
+ public async Task DequeueCoinsFromMixAsync(TxoRef coin, DequeueReason reason)
{
await DequeueCoinsFromMixAsync(new[] { coin }, reason).ConfigureAwait(false);
}
- public async Task DequeueCoinsFromMixAsync(TxoRef[] coins, string reason)
+ public async Task DequeueCoinsFromMixAsync(TxoRef[] coins, DequeueReason reason)
{
if (coins is null || !coins.Any())
{
@@ -911,7 +910,7 @@ namespace WalletWasabi.CoinJoin.Client.Clients
}
}
- public async Task DequeueAllCoinsFromMixAsync(string reason)
+ public async Task DequeueAllCoinsFromMixAsync(DequeueReason reason)
{
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(3));
try
@@ -927,28 +926,30 @@ namespace WalletWasabi.CoinJoin.Client.Clients
}
}
- private async Task DequeueAllCoinsFromMixNoLockAsync(string reason)
+ private async Task DequeueAllCoinsFromMixNoLockAsync(DequeueReason reason)
{
await DequeueCoinsFromMixNoLockAsync(State.GetAllQueuedCoins().ToArray(), reason).ConfigureAwait(false);
}
private async Task DequeueSpentCoinsFromMixNoLockAsync()
{
- await DequeueCoinsFromMixNoLockAsync(State.GetSpentCoins().ToArray()).ConfigureAwait(false);
+ await DequeueCoinsFromMixNoLockAsync(State.GetSpentCoins().ToArray(), DequeueReason.Spent).ConfigureAwait(false);
}
- private async Task DequeueCoinsFromMixNoLockAsync(TxoRef coin, string reason = null)
+ private async Task DequeueCoinsFromMixNoLockAsync(TxoRef coin, DequeueReason reason)
{
await DequeueCoinsFromMixNoLockAsync(new[] { coin }, reason).ConfigureAwait(false);
}
- private async Task DequeueCoinsFromMixNoLockAsync(TxoRef[] coins, string reason = null)
+ private async Task<DequeueResult> DequeueCoinsFromMixNoLockAsync(TxoRef[] coins, DequeueReason reason)
{
if (coins is null || !coins.Any())
{
- return;
+ return new DequeueResult(ImmutableDictionary<DequeueReason, IEnumerable<SmartCoin>>.Empty, ImmutableDictionary<DequeueReason, IEnumerable<SmartCoin>>.Empty);
}
+ var successful = new Dictionary<DequeueReason, List<SmartCoin>>();
+ var unsuccessful = new Dictionary<DequeueReason, List<SmartCoin>>();
List<Exception> exceptions = new List<Exception>();
foreach (var coinReference in coins)
@@ -959,15 +960,10 @@ namespace WalletWasabi.CoinJoin.Client.Clients
continue;
}
- foreach (long roundId in State.GetPassivelyMixingRounds())
+ foreach (var round in State.GetAllMixingRounds().Where(x => x.CoinsRegistered.Contains(coinToDequeue)))
{
- var round = State.GetSingleOrDefaultRound(roundId);
- if (round is null)
- {
- throw new NotSupportedException("This is impossible.");
- }
-
- if (round.CoinsRegistered.Contains(coinToDequeue))
+ Exception exception = null;
+ if (round.State.Phase == RoundPhase.InputRegistration)
{
try
{
@@ -978,41 +974,47 @@ namespace WalletWasabi.CoinJoin.Client.Clients
{
if (coinToDequeue.Unspent)
{
- exceptions.Add(ex);
- }
+ exception = ex;
}
}
}
-
- foreach (long roundId in State.GetActivelyMixingRounds())
+ else
{
- var round = State.GetSingleOrDefaultRound(roundId);
- if (round is null)
+ if (coinToDequeue.Unspent) // If coin was spent, well that sucks, except if it was spent by the tumbler in signing phase.
{
- continue;
+ exception = new NotSupportedException($"Cannot deque coin in {round.State.Phase} phase. Coin: {coinToDequeue.Index}:{coinToDequeue.TransactionId}.");
}
-
- if (round.CoinsRegistered.Contains(coinToDequeue))
- {
- if (!coinToDequeue.Unspent) // If coin was spent, well that sucks, except if it was spent by the tumbler in signing phase.
+ else
{
State.ClearRoundRegistration(round.State.RoundId);
- continue;
}
- else
- {
- exceptions.Add(new NotSupportedException($"Cannot deque coin in {round.State.Phase} phase. Coin: {coinToDequeue.Index}:{coinToDequeue.TransactionId}."));
}
+
+ if (exception is { })
+ {
+ exceptions.Add(exception);
+ unsuccessful.AddToValueList(DequeueReason.Mixing, coinToDequeue);
}
}
SmartCoin coinWaitingForMix = State.GetSingleOrDefaultFromWaitingList(coinToDequeue);
- if (coinWaitingForMix != null) // If it is not being mixed, we can just remove it.
+ if (coinWaitingForMix is { }) // If it is not being mixed, we can just remove it.
{
- RemoveCoin(new DequeueCoin(coinWaitingForMix, reason));
+ State.RemoveCoinFromWaitingList(coinWaitingForMix);
+ coinWaitingForMix.CoinJoinInProgress = false;
+ coinWaitingForMix.Secret = null;
+ successful.AddToValueList(reason, coinToDequeue);
+ Logger.LogInfo($"Coin dequeued: {coinWaitingForMix.Index}:{coinWaitingForMix.TransactionId}. Reason: {reason}.");
}
}
+ var result = new DequeueResult(successful.ToDictionary(x => x.Key, x => x.Value as IEnumerable<SmartCoin>), unsuccessful.ToDictionary(x => x.Key, x => x.Value as IEnumerable<SmartCoin>));
+
+ if (result.Successful.Any())
+ {
+ OnDequeued?.Invoke(this, result);
+ }
+
if (exceptions.Count == 1)
{
throw exceptions.Single();
@@ -1022,16 +1024,7 @@ namespace WalletWasabi.CoinJoin.Client.Clients
{
throw new AggregateException(exceptions);
}
- }
-
- private void RemoveCoin(DequeueCoin coinWaitingForMix)
- {
- State.RemoveCoinFromWaitingList(coinWaitingForMix.Coin);
- coinWaitingForMix.Coin.CoinJoinInProgress = false;
- coinWaitingForMix.Coin.Secret = null;
- CoinDequeued?.Invoke(this, coinWaitingForMix);
- var reasonText = coinWaitingForMix.HasReason ? $" Reason: {coinWaitingForMix.Reason}" : "";
- Logger.LogInfo($"Coin dequeued: {coinWaitingForMix.Coin.Index}:{coinWaitingForMix.Coin.TransactionId}.{reasonText}.");
+ return result;
}
public async Task StopAsync()
@@ -1064,7 +1057,7 @@ namespace WalletWasabi.CoinJoin.Client.Clients
{
continue; // The coin is not present anymore. Good. This should never happen though.
}
- await DequeueCoinsFromMixNoLockAsync(coin.GetTxoRef(), "Stopping Wasabi.").ConfigureAwait(false);
+ await DequeueCoinsFromMixNoLockAsync(coin.GetTxoRef(), DequeueReason.ApplicationExit).ConfigureAwait(false);
}
catch (Exception ex)
{

ADD
WalletWasabi/CoinJoin/Client/Clients/Queuing/DequeueReason.cs
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace WalletWasabi.CoinJoin.Client.Clients.Queuing
+{
+ public enum DequeueReason
+ {
+ UserRequested, // Success, user requested the dequeue.
+ TransactionBuilding, // Success, transaction is being build with the coins.
+ Banned, // Success
+ NotEnoughFundsEnqueued, // Success
+ CoordinatorFeeChanged, // Success
+ ApplicationExit, // Success, application is exiting.
+ Spent, // Success, coin is spent.
+ Mixing // Failure, the coin's mixing status is > registration.
+ }
+}

ADD
WalletWasabi/CoinJoin/Client/Clients/Queuing/DequeueResult.cs
+using System;
+using System.Collections.Generic;
+using System.Text;
+using WalletWasabi.Blockchain.TransactionOutputs;
+using WalletWasabi.Helpers;
+
+namespace WalletWasabi.CoinJoin.Client.Clients.Queuing
+{
+ public class DequeueResult
+ {
+ public IDictionary<DequeueReason, IEnumerable<SmartCoin>> Successful { get; }
+ public IDictionary<DequeueReason, IEnumerable<SmartCoin>> Unsuccessful { get; }
+
+ public DequeueResult(IDictionary<DequeueReason, IEnumerable<SmartCoin>> successful, IDictionary<DequeueReason, IEnumerable<SmartCoin>> unsuccessful)
+ {
+ Successful = Guard.NotNull(nameof(successful), successful);
+ Unsuccessful = Guard.NotNull(nameof(unsuccessful), unsuccessful);
+ }
+ }
+}

MODIFY
WalletWasabi/CoinJoin/Client/Rounds/ClientState.cs
WalletWasabi/CoinJoin/Client/Rounds/ClientState.cs
@@ -319,27 +319,27 @@ namespace WalletWasabi.CoinJoin.Client.Rounds
}
}
- public IEnumerable<long> GetActivelyMixingRounds()
+ public IEnumerable<ClientRound> GetActivelyMixingRounds()
{
lock (StateLock)
{
- return Rounds.Where(x => !(x.Registration is null) && x.State.Phase >= RoundPhase.ConnectionConfirmation).Select(x => x.State.RoundId).ToArray();
+ return Rounds.Where(x => x.Registration is { } && x.State.Phase >= RoundPhase.ConnectionConfirmation).ToArray();
}
}
- public IEnumerable<long> GetPassivelyMixingRounds()
+ public IEnumerable<ClientRound> GetPassivelyMixingRounds()
{
lock (StateLock)
{
- return Rounds.Where(x => !(x.Registration is null) && x.State.Phase == RoundPhase.InputRegistration).Select(x => x.State.RoundId).ToArray();
+ return Rounds.Where(x => x.Registration is { } && x.State.Phase == RoundPhase.InputRegistration).ToArray();
}
}
- public IEnumerable<long> GetAllMixingRounds()
+ public IEnumerable<ClientRound> GetAllMixingRounds()
{
lock (StateLock)
{
- return Rounds.Where(x => !(x.Registration is null)).Select(x => x.State.RoundId).ToArray();
+ return Rounds.Where(x => x.Registration is { }).ToArray();
}
}

DELETE
WalletWasabi/CoinJoin/Client/Rounds/DequeueCoin.cs
-using System;
-using System.Collections.Generic;
-using System.Text;
-using WalletWasabi.Blockchain.TransactionOutputs;
-using WalletWasabi.Helpers;
-
-namespace WalletWasabi.CoinJoin.Client.Rounds
-{
- public class DequeueCoin
- {
- public DequeueCoin(SmartCoin coin, string reason)
- {
- Coin = Guard.NotNull(nameof(coin), coin);
- Reason = Guard.Correct(reason);
- }
-
- public SmartCoin Coin { get; }
- public string Reason { get; }
-
- public bool HasReason => Reason.Length != 0;
- }
-}

MODIFY
WalletWasabi/CoinJoin/Common/Models/RoundStateResponse.cs
WalletWasabi/CoinJoin/Common/Models/RoundStateResponse.cs
@@ -66,7 +66,7 @@ namespace WalletWasabi.CoinJoin.Common.Models
//return baseMinimum + (FeePerInputs * MaximumInputCountPerPeer);
}
- public bool HaveEnoughQueued(params Money[] queuedCoinAmounts)
+ public bool HaveEnoughQueued(IEnumerable<Money> queuedCoinAmounts)
{
var tried = new List<Money>();
Money baseMinimum = Denomination + (FeePerOutputs * 2); // + (Denomination.Percentange(CoordinatorFeePercent) * RequiredPeerCount);

MODIFY
WalletWasabi/Extensions/LinqExtensions.cs
WalletWasabi/Extensions/LinqExtensions.cs
@@ -59,6 +59,18 @@ namespace System.Linq
}
}
+ public static void AddToValueList<TKey, TValue, Telem>(this Dictionary<TKey, TValue> myDic, TKey key, Telem elem) where TValue : List<Telem>
+ {
+ if (myDic.ContainsKey(key))
+ {
+ myDic[key].Add(elem);
+ }
+ else
+ {
+ myDic.Add(key, new List<Telem>() { elem } as TValue);
+ }
+ }
+
// https://stackoverflow.com/a/2992364
public static void RemoveByValue<TKey, TValue>(this SortedDictionary<TKey, TValue> me, TValue value)
{

Improve dequeue messages