Add all round predicate
MODIFY
WalletWasabi.Tests/UnitTests/WabiSabi/Client/CoinJoinClientTests.cs
WalletWasabi.Tests/UnitTests/WabiSabi/Client/CoinJoinClientTests.cs
@@ -56,7 +56,7 @@ namespace WalletWasabi.Tests.UnitTests.WabiSabi.Client
using var roundStateUpdater = new RoundStatusUpdater(TimeSpan.FromSeconds(1), wabiSabiApi);
await roundStateUpdater.StartAsync(CancellationToken.None);
- using CoinJoinClient coinJoinClient = new(roundState.Id, wabiSabiApi, new[] { smartCoin.Coin }, kitchen, km, roundStateUpdater);
+ using CoinJoinClient coinJoinClient = new(wabiSabiApi, new[] { smartCoin.Coin }, kitchen, km, roundStateUpdater);
await coinJoinClient.StartAsync(CancellationToken.None);
await coinJoinClient.StopAsync(CancellationToken.None);
await roundStateUpdater.StopAsync(CancellationToken.None);

MODIFY
WalletWasabi.Tests/UnitTests/WabiSabi/Integration/WabiSabiHttpApiIntegrationTests.cs
WalletWasabi.Tests/UnitTests/WabiSabi/Integration/WabiSabiHttpApiIntegrationTests.cs
@@ -113,7 +113,7 @@ namespace WalletWasabi.Tests.UnitTests.WabiSabi.Integration
using var roundStateUpdater = new RoundStatusUpdater(TimeSpan.FromSeconds(1), apiClient);
await roundStateUpdater.StartAsync(CancellationToken.None);
- using var coinJoinClient = new CoinJoinClient(roundState.Id, apiClient, coins, kitchen, keyManager, roundStateUpdater);
+ using var coinJoinClient = new CoinJoinClient(apiClient, coins, kitchen, keyManager, roundStateUpdater);
// Run the coinjoin client task.
await coinJoinClient.StartAsync(CancellationToken.None);

MODIFY
WalletWasabi/WabiSabi/Client/CoinJoinClient.cs
WalletWasabi/WabiSabi/Client/CoinJoinClient.cs
@@ -23,14 +23,12 @@ namespace WalletWasabi.WabiSabi.Client
private bool _disposedValue;
public CoinJoinClient(
- uint256 roundId,
IWabiSabiApiRequestHandler arenaRequestHandler,
IEnumerable<Coin> coins,
Kitchen kitchen,
KeyManager keymanager,
RoundStatusUpdater roundStatusUpdater)
{
- RoundId = roundId;
ArenaRequestHandler = arenaRequestHandler;
Kitchen = kitchen;
Keymanager = keymanager;
@@ -39,8 +37,6 @@ namespace WalletWasabi.WabiSabi.Client
Coins = coins;
}
- private uint256 RoundId { get; }
- private RoundState RoundState { get; set; }
private ZeroCredentialPool ZeroAmountCredentialPool { get; } = new();
private ZeroCredentialPool ZeroVsizeCredentialPool { get; } = new();
private SecureRandom SecureRandom { get; }
@@ -56,10 +52,11 @@ namespace WalletWasabi.WabiSabi.Client
{
try
{
- var constructionState = RoundState.Assert<ConstructionState>();
+ var roundState = await RoundStatusUpdater.CreateRoundAwaiter(roundState => roundState.Phase == Phase.InputRegistration, cancellationToken).ConfigureAwait(false);
+ var constructionState = roundState.Assert<ConstructionState>();
// Calculate outputs values
- var outputValues = DecomposeAmounts();
+ var outputValues = DecomposeAmounts(roundState);
// Get all locked internal keys we have and assert we have enough.
Keymanager.AssertLockedInternalKeysIndexed(howMany: Coins.Count());
@@ -71,26 +68,26 @@ namespace WalletWasabi.WabiSabi.Client
Coins.Select(x => (ulong)x.ScriptPubKey.EstimateInputVsize()),
outputValues);
- var aliceClients = CreateAliceClients();
+ var aliceClients = CreateAliceClients(roundState);
// Register coins.
- aliceClients = await RegisterCoinsAsync(aliceClients, cancellationToken).ConfigureAwait(false);
+ aliceClients = await RegisterCoinsAsync(aliceClients, roundState, cancellationToken).ConfigureAwait(false);
// Confirm coins.
- aliceClients = await ConfirmConnectionsAsync(aliceClients, cancellationToken).ConfigureAwait(false);
+ aliceClients = await ConfirmConnectionsAsync(aliceClients, roundState, cancellationToken).ConfigureAwait(false);
// Output registration.
- RoundState = await RoundStatusUpdater.CreateRoundAwaiter(RoundId, rs => rs.Phase == Phase.OutputRegistration, cancellationToken).ConfigureAwait(false);
+ roundState = await RoundStatusUpdater.CreateRoundAwaiter(roundState.Id, rs => rs.Phase == Phase.OutputRegistration, cancellationToken).ConfigureAwait(false);
var outputsWithCredentials = outputs.Zip(aliceClients, (output, alice) => (output, alice.RealAmountCredentials, alice.RealVsizeCredentials));
- await RegisterOutputsAsync(outputsWithCredentials, cancellationToken).ConfigureAwait(false);
+ await RegisterOutputsAsync(outputsWithCredentials, roundState, cancellationToken).ConfigureAwait(false);
// Signing.
- RoundState = await RoundStatusUpdater.CreateRoundAwaiter(RoundId, rs => rs.Phase == Phase.TransactionSigning, cancellationToken).ConfigureAwait(false);
- var signingState = RoundState.Assert<SigningState>();
+ roundState = await RoundStatusUpdater.CreateRoundAwaiter(roundState.Id, rs => rs.Phase == Phase.TransactionSigning, cancellationToken).ConfigureAwait(false);
+ var signingState = roundState.Assert<SigningState>();
var unsignedCoinJoin = signingState.CreateUnsignedTransaction();
// Sanity check.
- SanityCheck(outputs, unsignedCoinJoin, cancellationToken);
+ SanityCheck(outputs, unsignedCoinJoin, roundState, cancellationToken);
// Send signature.
await SignTransactionAsync(aliceClients, unsignedCoinJoin, cancellationToken).ConfigureAwait(false);
@@ -101,14 +98,14 @@ namespace WalletWasabi.WabiSabi.Client
}
}
- private List<AliceClient> CreateAliceClients()
+ private List<AliceClient> CreateAliceClients(RoundState roundState)
{
List<AliceClient> aliceClients = new();
foreach (var coin in Coins)
{
var aliceArenaClient = new ArenaClient(
- RoundState.AmountCredentialIssuerParameters,
- RoundState.VsizeCredentialIssuerParameters,
+ roundState.AmountCredentialIssuerParameters,
+ roundState.VsizeCredentialIssuerParameters,
ZeroAmountCredentialPool,
ZeroVsizeCredentialPool,
ArenaRequestHandler,
@@ -116,39 +113,39 @@ namespace WalletWasabi.WabiSabi.Client
var hdKey = Keymanager.GetSecrets(Kitchen.SaltSoup(), coin.ScriptPubKey).Single();
var secret = hdKey.PrivateKey.GetBitcoinSecret(Keymanager.GetNetwork());
- aliceClients.Add(new AliceClient(RoundState.Id, aliceArenaClient, coin, RoundState.FeeRate, secret));
+ aliceClients.Add(new AliceClient(roundState.Id, aliceArenaClient, coin, roundState.FeeRate, secret));
}
return aliceClients;
}
- private async Task<List<AliceClient>> RegisterCoinsAsync(IEnumerable<AliceClient> aliceClients, CancellationToken cancellationToken)
+ private async Task<List<AliceClient>> RegisterCoinsAsync(IEnumerable<AliceClient> aliceClients, RoundState roundState, 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))
{
- Logger.LogWarning($"Round ({RoundState.Id}), Alice ({request.Sender.AliceId}): {nameof(AliceClient.RegisterInputAsync)} failed, reason:'{request.Exception}'.");
+ Logger.LogWarning($"Round ({roundState.Id}), Alice ({request.Sender.AliceId}): {nameof(AliceClient.RegisterInputAsync)} failed, reason:'{request.Exception}'.");
}
return completedRequests.Where(x => x.Success).Select(x => x.Sender).ToList();
}
- private async Task<List<AliceClient>> ConfirmConnectionsAsync(IEnumerable<AliceClient> aliceClients, CancellationToken cancellationToken)
+ private async Task<List<AliceClient>> ConfirmConnectionsAsync(IEnumerable<AliceClient> aliceClients, RoundState roundState, CancellationToken cancellationToken)
{
var confirmationRequests = aliceClients.Select(alice => WrapCall(alice, alice.ConfirmConnectionAsync(TimeSpan.FromMilliseconds(Random.Next(100, 1_000)), cancellationToken))).ToArray();
var completedRequests = await Task.WhenAll(confirmationRequests).ConfigureAwait(false);
foreach (var request in completedRequests.Where(x => !x.Success))
{
- Logger.LogWarning($"Round ({RoundState.Id}), Alice ({request.Sender.AliceId}): {nameof(AliceClient.ConfirmConnectionAsync)} failed, reason:'{request.Exception}'.");
+ Logger.LogWarning($"Round ({roundState.Id}), Alice ({request.Sender.AliceId}): {nameof(AliceClient.ConfirmConnectionAsync)} failed, reason:'{request.Exception}'.");
}
return completedRequests.Where(x => x.Success).Select(x => x.Sender).ToList();
}
- private IEnumerable<Money> DecomposeAmounts()
+ private IEnumerable<Money> DecomposeAmounts(RoundState roundState)
{
- return Coins.Select(c => c.Amount - RoundState.FeeRate.GetFee(c.ScriptPubKey.EstimateInputVsize()));
+ return Coins.Select(c => c.Amount - roundState.FeeRate.GetFee(c.ScriptPubKey.EstimateInputVsize()));
}
private IEnumerable<IEnumerable<(ulong RealAmountCredentialValue, ulong RealVsizeCredentialValue, Money Value)>> CreatePlan(
@@ -161,9 +158,10 @@ namespace WalletWasabi.WabiSabi.Client
private async Task RegisterOutputsAsync(
IEnumerable<(TxOut Output, Credential[] RealAmountCredentials, Credential[] RealVsizeCredentials)> outputsWithCredentials,
+ RoundState roundState,
CancellationToken cancellationToken)
{
- var bobClients = Enumerable.Range(0, int.MaxValue).Select(_ => CreateBobClient());
+ var bobClients = Enumerable.Range(0, int.MaxValue).Select(_ => CreateBobClient(roundState));
var outputRegistrationData = outputsWithCredentials.Zip(
bobClients,
(o, b) => (
@@ -178,17 +176,17 @@ namespace WalletWasabi.WabiSabi.Client
foreach (var request in completedRequests.Where(x => !x.Success))
{
- Logger.LogWarning($"Round ({RoundState.Id}), Bob ({request.Sender.ScriptPubKey}): {nameof(BobClient.RegisterOutputAsync)} failed, reason:'{request.Exception}'.");
+ Logger.LogWarning($"Round ({roundState.Id}), Bob ({request.Sender.ScriptPubKey}): {nameof(BobClient.RegisterOutputAsync)} failed, reason:'{request.Exception}'.");
}
}
- private BobClient CreateBobClient()
+ private BobClient CreateBobClient(RoundState roundState)
{
return new BobClient(
- RoundState.Id,
+ roundState.Id,
new(
- RoundState.AmountCredentialIssuerParameters,
- RoundState.VsizeCredentialIssuerParameters,
+ roundState.AmountCredentialIssuerParameters,
+ roundState.VsizeCredentialIssuerParameters,
ZeroAmountCredentialPool,
ZeroVsizeCredentialPool,
ArenaRequestHandler,
@@ -201,13 +199,13 @@ namespace WalletWasabi.WabiSabi.Client
await base.StartAsync(linkedCts.Token).ConfigureAwait(false);
}
- private void SanityCheck(IEnumerable<TxOut> outputs, Transaction unsignedCoinJoinTransaction, CancellationToken cancellationToken)
+ private void SanityCheck(IEnumerable<TxOut> outputs, Transaction unsignedCoinJoinTransaction, RoundState roundState, CancellationToken cancellationToken)
{
var coinJoinOutputs = unsignedCoinJoinTransaction.Outputs.Select(o => (o.Value, o.ScriptPubKey));
var expectedOutputs = outputs.Select(o => (o.Value, o.ScriptPubKey));
if (coinJoinOutputs.IsSuperSetOf(expectedOutputs))
{
- throw new InvalidOperationException($"Round ({RoundState.Id}): My output is missing.");
+ throw new InvalidOperationException($"Round ({roundState.Id}): My output is missing.");
}
}

MODIFY
WalletWasabi/WabiSabi/Client/RoundStatusUpdater.cs
WalletWasabi/WabiSabi/Client/RoundStatusUpdater.cs
@@ -57,6 +57,14 @@ namespace WalletWasabi.WabiSabi.Client
RoundStates = updatedRoundStates.Union(newRoundStates).ToDictionary(s => s.Key, s => s.Value);
+ if (Awaiters.TryGetValue(uint256.Zero, out var taskAndPredicateList))
+ {
+ foreach (var roundState in RoundStates.Values)
+ {
+ HandleTasks(taskAndPredicateList, roundState);
+ }
+ }
+
if (roundsToUpdate.Any())
{
ExecuteAwaiters(roundsToUpdate, RoundStates);
@@ -69,7 +77,7 @@ namespace WalletWasabi.WabiSabi.Client
{
foreach (var roundId in roundsToUpdate)
{
- if (roundStates.TryGetValue(roundId, out RoundState? roundState))
+ if (!roundStates.TryGetValue(roundId, out var roundState))
{
// The round is missing.
var tasks = Awaiters[roundId];
@@ -81,16 +89,23 @@ namespace WalletWasabi.WabiSabi.Client
continue;
}
- var taskAndPredicateList = Awaiters[roundId];
- foreach (var taskAndPredicate in taskAndPredicateList.Where(taskAndPredicate => taskAndPredicate.Predicate(roundState!)).ToArray())
+ if (roundState is not null && Awaiters.TryGetValue(roundId, out var taskAndPredicateList))
+ {
+ HandleTasks(taskAndPredicateList, roundState);
+ }
+ }
+ }
+
+ private static void HandleTasks(List<(TaskCompletionSource<RoundState> Task, Predicate<RoundState> Predicate)> taskAndPredicateList, RoundState roundState)
+ {
+ foreach (var taskAndPredicate in taskAndPredicateList.Where(taskAndPredicate => taskAndPredicate.Predicate(roundState)).ToArray())
{
// The predicate was fulfilled.
var task = taskAndPredicate.Task;
- task.TrySetResult(roundState!);
+ task.TrySetResult(roundState);
taskAndPredicateList.Remove(taskAndPredicate);
}
}
- }
public Task<RoundState> CreateRoundAwaiter(uint256 roundId, Predicate<RoundState> predicate, CancellationToken cancellationToken)
{
@@ -112,6 +127,11 @@ namespace WalletWasabi.WabiSabi.Client
return tcs.Task;
}
+ public Task<RoundState> CreateRoundAwaiter(Predicate<RoundState> predicate, CancellationToken cancellationToken)
+ {
+ return CreateRoundAwaiter(uint256.Zero, predicate, cancellationToken);
+ }
+
public override Task StopAsync(CancellationToken cancellationToken)
{
foreach (var t in Awaiters.SelectMany(a => a.Value).Select(a => a.Task))

Add all round predicate