Make CoinJoinClient a normal class
MODIFY
WalletWasabi.Tests/UnitTests/WabiSabi/Client/CoinJoinClientTests.cs
WalletWasabi.Tests/UnitTests/WabiSabi/Client/CoinJoinClientTests.cs
@@ -53,8 +53,8 @@ namespace WalletWasabi.Tests.UnitTests.WabiSabi.Client
var wabiSabiApi = new WabiSabiController(coordinator);
- using CoinJoinClient coinJoinClient = new(roundState.Id, wabiSabiApi, new[] { smartCoin.Coin }, kitchen, km);
- await coinJoinClient.StartAsync(CancellationToken.None);
+ CoinJoinClient coinJoinClient = new(roundState.Id, wabiSabiApi, new[] { smartCoin.Coin }, kitchen, km);
+ await coinJoinClient.StartCoinJoinAsync(CancellationToken.None);
}
}
}

MODIFY
WalletWasabi.Tests/UnitTests/WabiSabi/Integration/WabiSabiHttpApiIntegrationTests.cs
WalletWasabi.Tests/UnitTests/WabiSabi/Integration/WabiSabiHttpApiIntegrationTests.cs
@@ -107,15 +107,13 @@ namespace WalletWasabi.Tests.UnitTests.WabiSabi.Integration
var roundState = rounds.First(x => x.CoinjoinState is ConstructionState);
var kitchen = new Kitchen();
kitchen.Cook("");
- using var coinJoinClient = new CoinJoinClient(roundState.Id, apiClient, coins, kitchen, keyManager);
+ var coinJoinClient = new CoinJoinClient(roundState.Id, apiClient, coins, kitchen, keyManager);
// Run the coinjoin client task.
- await coinJoinClient.StartAsync(CancellationToken.None);
+ await coinJoinClient.StartCoinJoinAsync(cts.Token);
var boadcastedTx = await transactionCompleted.Task.ConfigureAwait(false); // wait for the transaction to be broadcasted.
Assert.NotNull(boadcastedTx);
-
- await coinJoinClient.StopAsync(CancellationToken.None);
}
[Fact]

MODIFY
WalletWasabi/WabiSabi/Client/CoinJoinClient.cs
WalletWasabi/WabiSabi/Client/CoinJoinClient.cs
@@ -18,10 +18,8 @@ using WalletWasabi.Wallets;
namespace WalletWasabi.WabiSabi.Client
{
- public class CoinJoinClient : BackgroundService, IDisposable
+ public class CoinJoinClient
{
- private bool _disposedValue;
-
public CoinJoinClient(
uint256 roundId,
IWabiSabiApiRequestHandler arenaRequestHandler,
@@ -33,7 +31,6 @@ namespace WalletWasabi.WabiSabi.Client
ArenaRequestHandler = arenaRequestHandler;
Kitchen = kitchen;
Keymanager = keymanager;
- SecureRandom = new SecureRandom();
Coins = coins;
}
@@ -41,17 +38,14 @@ namespace WalletWasabi.WabiSabi.Client
private RoundState RoundState { get; set; }
private ZeroCredentialPool ZeroAmountCredentialPool { get; } = new();
private ZeroCredentialPool ZeroVsizeCredentialPool { get; } = new();
- private SecureRandom SecureRandom { get; }
- private CancellationTokenSource DisposeCts { get; } = new();
private IEnumerable<Coin> Coins { get; set; }
+ private SecureRandom SecureRandom { get; } = new SecureRandom();
private Random Random { get; } = new();
public IWabiSabiApiRequestHandler ArenaRequestHandler { get; }
public Kitchen Kitchen { get; }
public KeyManager Keymanager { get; }
- protected override async Task ExecuteAsync(CancellationToken cancellationToken)
- {
- try
+ public async Task StartCoinJoinAsync(CancellationToken cancellationToken)
{
await RefreshRoundAsync(cancellationToken).ConfigureAwait(false);
var constructionState = RoundState.Assert<ConstructionState>();
@@ -69,7 +63,7 @@ namespace WalletWasabi.WabiSabi.Client
Coins.Select(x => (ulong)x.ScriptPubKey.EstimateInputVsize()),
outputValues);
- var aliceClients = CreateAliceClients();
+ List<AliceClient> aliceClients = CreateAliceClients();
// Register coins.
aliceClients = await RegisterCoinsAsync(aliceClients, cancellationToken).ConfigureAwait(false);
@@ -94,11 +88,6 @@ namespace WalletWasabi.WabiSabi.Client
// Send signature.
await SignTransactionAsync(aliceClients, unsignedCoinJoin, cancellationToken).ConfigureAwait(false);
}
- catch (Exception ex)
- {
- // The game is over for this round, no fallback mechanism. In the next round we will create another CoinJoinClient and try again.
- }
- }
private async Task WaitFor(Phase expectedPhase, CancellationToken cancellationToken)
{
@@ -141,27 +130,46 @@ namespace WalletWasabi.WabiSabi.Client
private async Task<List<AliceClient>> RegisterCoinsAsync(IEnumerable<AliceClient> aliceClients, CancellationToken cancellationToken)
{
- var registerRequests = aliceClients.Select(alice => WrapCall(alice, alice.RegisterInputAsync(cancellationToken)));
- var completedRequests = await Task.WhenAll(registerRequests).ConfigureAwait(false);
-
- foreach (var request in completedRequests.Where(x => !x.Success))
+ async Task<AliceClient?> RegisterInputTask(AliceClient aliceClient)
{
- Logger.LogWarning($"Round ({RoundState.Id}), Alice ({request.Sender.AliceId}): {nameof(AliceClient.RegisterInputAsync)} failed, reason:'{request.Exception}'.");
+ try
+ {
+ await aliceClient.RegisterInputAsync(cancellationToken).ConfigureAwait(false);
+ return aliceClient;
}
- return completedRequests.Where(x => x.Success).Select(x => x.Sender).ToList();
+ catch (Exception e)
+ {
+ Logger.LogWarning($"Round ({RoundState.Id}), Alice ({aliceClient.AliceId}): {nameof(AliceClient.RegisterInputAsync)} failed, reason:'{e}'.");
+ return default;
+ }
+ }
+
+ var registerRequests = aliceClients.Select(RegisterInputTask);
+ var completedRequests = await Task.WhenAll(registerRequests).ConfigureAwait(false);
+
+ return completedRequests.Where(x => x is not null).Cast<AliceClient>().ToList();
}
private async Task<List<AliceClient>> ConfirmConnectionsAsync(IEnumerable<AliceClient> aliceClients, CancellationToken cancellationToken)
{
- var confirmationRequests = aliceClients.Select(alice => WrapCall(alice, alice.ConfirmConnectionAsync(cancellationToken))).ToArray();
- var completedRequests = await Task.WhenAll(confirmationRequests).ConfigureAwait(false);
-
- foreach (var request in completedRequests.Where(x => !x.Success))
+ async Task<AliceClient?> ConfirmConnectionTask(AliceClient aliceClient)
+ {
+ try
+ {
+ await aliceClient.ConfirmConnectionAsync(cancellationToken).ConfigureAwait(false);
+ return aliceClient;
+ }
+ catch (Exception e)
{
- Logger.LogWarning($"Round ({RoundState.Id}), Alice ({request.Sender.AliceId}): {nameof(AliceClient.ConfirmConnectionAsync)} failed, reason:'{request.Exception}'.");
+ Logger.LogWarning($"Round ({RoundState.Id}), Alice ({aliceClient.AliceId}): {nameof(AliceClient.ConfirmConnectionAsync)} failed, reason:'{e}'.");
+ return default;
+ }
}
- return completedRequests.Where(x => x.Success).Select(x => x.Sender).ToList();
+ var confirmationRequests = aliceClients.Select(ConfirmConnectionTask);
+ var completedRequests = await Task.WhenAll(confirmationRequests).ConfigureAwait(false);
+
+ return completedRequests.Where(x => x is not null).Cast<AliceClient>().ToList();
}
private IEnumerable<Money> DecomposeAmounts()
@@ -181,25 +189,28 @@ namespace WalletWasabi.WabiSabi.Client
IEnumerable<(TxOut Output, Credential[] RealAmountCredentials, Credential[] RealVsizeCredentials)> outputsWithCredentials,
CancellationToken cancellationToken)
{
- var bobClients = Enumerable.Range(0, int.MaxValue).Select(_ => CreateBobClient());
- var outputRegistrationData = outputsWithCredentials.Zip(
- bobClients,
- (o, b) => (
- TxOut: o.Output,
- RealAmountCredentials: o.RealAmountCredentials,
- RealVsizeCredentials: o.RealVsizeCredentials,
- BobClient: b));
-
- var outputRegisterRequests = outputRegistrationData
- .Select(x => WrapCall(x.TxOut, x.BobClient.RegisterOutputAsync(x.TxOut.Value, x.TxOut.ScriptPubKey, x.RealAmountCredentials, x.RealVsizeCredentials, cancellationToken)));
- var completedRequests = await Task.WhenAll(outputRegisterRequests).ConfigureAwait(false);
-
- foreach (var request in completedRequests.Where(x => !x.Success))
+ async Task<TxOut?> RegisterOutputTask(BobClient bobClient, TxOut output, Credential[] realAmountCredentials, Credential[] realVsizeCredentials)
{
- Logger.LogWarning($"Round ({RoundState.Id}), Bob ({request.Sender.ScriptPubKey}): {nameof(BobClient.RegisterOutputAsync)} failed, reason:'{request.Exception}'.");
+ try
+ {
+ await bobClient.RegisterOutputAsync(output.Value, output.ScriptPubKey, realAmountCredentials, realVsizeCredentials, cancellationToken).ConfigureAwait(false);
+ return output;
+ }
+ catch (Exception e)
+ {
+ Logger.LogWarning($"Round ({RoundState.Id}), Bob ({{output.ScriptPubKey}}): {nameof(BobClient.RegisterOutputAsync)} failed, reason:'{e}'.");
+ return default;
}
}
+ var bobClients = Enumerable.Range(0, int.MaxValue).Select(_ => CreateBobClient());
+ var outputRegisterRequests = bobClients.Zip(
+ outputsWithCredentials,
+ (bobClient, data) => RegisterOutputTask(bobClient, data.Output, data.RealAmountCredentials, data.RealVsizeCredentials));
+
+ await Task.WhenAll(outputRegisterRequests).ConfigureAwait(false);
+ }
+
private BobClient CreateBobClient()
{
return new BobClient(
@@ -213,12 +224,6 @@ namespace WalletWasabi.WabiSabi.Client
SecureRandom));
}
- public async override Task StartAsync(CancellationToken cancellationToken)
- {
- using var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(DisposeCts.Token, cancellationToken);
- await base.StartAsync(linkedCts.Token).ConfigureAwait(false);
- }
-
private void SanityCheck(IEnumerable<TxOut> outputs, Transaction unsignedCoinJoinTransaction, CancellationToken cancellationToken)
{
var coinJoinOutputs = unsignedCoinJoinTransaction.Outputs.Select(o => (o.Value, o.ScriptPubKey));
@@ -231,48 +236,22 @@ namespace WalletWasabi.WabiSabi.Client
private async Task SignTransactionAsync(IEnumerable<AliceClient> aliceClients, Transaction unsignedCoinJoinTransaction, CancellationToken cancellationToken)
{
- foreach (var aliceClient in aliceClients)
- {
- await aliceClient.SignTransactionAsync(unsignedCoinJoinTransaction, cancellationToken).ConfigureAwait(false);
- }
- }
-
- private async Task<(bool Success, TSender Sender, Exception? Exception)> WrapCall<TSender>(TSender sender, Task task)
+ async Task<AliceClient?> SignTransactionTask(AliceClient aliceClient)
{
try
{
- await task.ConfigureAwait(false);
- return (true, sender, default);
+ await aliceClient.SignTransactionAsync(unsignedCoinJoinTransaction, cancellationToken).ConfigureAwait(false);
+ return aliceClient;
}
catch (Exception e)
{
- return (false, sender, e);
- }
- }
-
- public async override Task StopAsync(CancellationToken cancellationToken)
- {
- using var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(DisposeCts.Token, cancellationToken);
- await base.StopAsync(linkedCts.Token).ConfigureAwait(false);
- }
-
- protected virtual void Dispose(bool disposing)
- {
- if (!_disposedValue)
- {
- if (disposing)
- {
- DisposeCts.Cancel();
- SecureRandom.Dispose();
- }
- _disposedValue = true;
+ Logger.LogWarning($"Round ({RoundState.Id}), Alice ({{aliceClient.AliceId}}): {nameof(AliceClient.SignTransactionAsync)} failed, reason:'{e}'.");
+ return default;
}
}
- public void Dispose()
- {
- Dispose(disposing: true);
- GC.SuppressFinalize(this);
+ var signingRequests = aliceClients.Select(SignTransactionTask);
+ await Task.WhenAll(signingRequests).ConfigureAwait(false);
}
}
}

Make CoinJoinClient a normal class