Turtles all the way down
MODIFY
WalletWasabi.Tests/UnitTests/WabiSabi/Backend/AliceTimeoutTests.cs
WalletWasabi.Tests/UnitTests/WabiSabi/Backend/AliceTimeoutTests.cs
@@ -88,7 +88,7 @@ namespace WalletWasabi.Tests.UnitTests.WabiSabi.Backend
Assert.Single(round.Alices);
DateTimeOffset preDeadline = DateTimeOffset.UtcNow - TimeSpan.FromMilliseconds(1);
alice.Deadline = preDeadline;
- handler.ConfirmConnection(req);
+ await handler.ConfirmConnectionAsync(req);
await arena.TriggerAndWaitRoundAsync(TimeSpan.FromSeconds(21));
Assert.Single(round.Alices);
Assert.NotEqual(preDeadline, alice.Deadline);

MODIFY
WalletWasabi.Tests/UnitTests/WabiSabi/Backend/ConfirmConnectionTests.cs
WalletWasabi.Tests/UnitTests/WabiSabi/Backend/ConfirmConnectionTests.cs
@@ -31,7 +31,7 @@ namespace WalletWasabi.Tests.UnitTests.WabiSabi.Backend
var req = WabiSabiFactory.CreateConnectionConfirmationRequest(round);
var minAliceDeadline = DateTimeOffset.UtcNow + cfg.ConnectionConfirmationTimeout * 0.9;
await using PostRequestHandler handler = new(cfg, new Prison(), arena, new MockRpcClient());
- var resp = handler.ConfirmConnection(req);
+ var resp = await handler.ConfirmConnectionAsync(req);
Assert.NotNull(resp);
Assert.NotNull(resp.ZeroAmountCredentials);
Assert.NotNull(resp.ZeroWeightCredentials);
@@ -56,7 +56,7 @@ namespace WalletWasabi.Tests.UnitTests.WabiSabi.Backend
var req = WabiSabiFactory.CreateConnectionConfirmationRequest(round);
await using PostRequestHandler handler = new(cfg, new Prison(), arena, new MockRpcClient());
- var resp = handler.ConfirmConnection(req);
+ var resp = await handler.ConfirmConnectionAsync(req);
Assert.NotNull(resp);
Assert.NotNull(resp.ZeroAmountCredentials);
Assert.NotNull(resp.ZeroWeightCredentials);
@@ -73,7 +73,7 @@ namespace WalletWasabi.Tests.UnitTests.WabiSabi.Backend
using Arena arena = await WabiSabiFactory.CreateAndStartArenaAsync();
await using PostRequestHandler handler = new(new WabiSabiConfig(), new Prison(), arena, new MockRpcClient());
var req = WabiSabiFactory.CreateConnectionConfirmationRequest();
- var ex = Assert.Throws<WabiSabiProtocolException>(() => handler.ConfirmConnection(req));
+ var ex = await Assert.ThrowsAsync<WabiSabiProtocolException>(async () => await handler.ConfirmConnectionAsync(req));
Assert.Equal(WabiSabiProtocolErrorCode.RoundNotFound, ex.ErrorCode);
await arena.StopAsync(CancellationToken.None);
@@ -96,7 +96,7 @@ namespace WalletWasabi.Tests.UnitTests.WabiSabi.Backend
{
round.Phase = phase;
await using PostRequestHandler handler = new(cfg, new Prison(), arena, new MockRpcClient());
- var ex = Assert.Throws<WabiSabiProtocolException>(() => handler.ConfirmConnection(req));
+ var ex = await Assert.ThrowsAsync<WabiSabiProtocolException>(async () => await handler.ConfirmConnectionAsync(req));
Assert.Equal(WabiSabiProtocolErrorCode.WrongPhase, ex.ErrorCode);
}
}
@@ -114,7 +114,7 @@ namespace WalletWasabi.Tests.UnitTests.WabiSabi.Backend
var req = WabiSabiFactory.CreateConnectionConfirmationRequest(round);
await using PostRequestHandler handler = new(cfg, new Prison(), arena, new MockRpcClient());
- var ex = Assert.Throws<WabiSabiProtocolException>(() => handler.ConfirmConnection(req));
+ var ex = await Assert.ThrowsAsync<WabiSabiProtocolException>(async () => await handler.ConfirmConnectionAsync(req));
Assert.Equal(WabiSabiProtocolErrorCode.AliceNotFound, ex.ErrorCode);
await arena.StopAsync(CancellationToken.None);
@@ -139,7 +139,7 @@ namespace WalletWasabi.Tests.UnitTests.WabiSabi.Backend
req.ZeroWeightCredentialRequests,
WabiSabiFactory.CreateRealCredentialRequests(round, null, 234).weightReq);
await using PostRequestHandler handler = new(cfg, new Prison(), arena, new MockRpcClient());
- var ex = Assert.Throws<WabiSabiProtocolException>(() => handler.ConfirmConnection(req));
+ var ex = await Assert.ThrowsAsync<WabiSabiProtocolException>(async () => await handler.ConfirmConnectionAsync(req));
Assert.Equal(WabiSabiProtocolErrorCode.IncorrectRequestedWeightCredentials, ex.ErrorCode);
await arena.StopAsync(CancellationToken.None);
@@ -164,7 +164,7 @@ namespace WalletWasabi.Tests.UnitTests.WabiSabi.Backend
req.ZeroWeightCredentialRequests,
req.RealWeightCredentialRequests);
await using PostRequestHandler handler = new(cfg, new Prison(), arena, new MockRpcClient());
- var ex = Assert.Throws<WabiSabiProtocolException>(() => handler.ConfirmConnection(req));
+ var ex = await Assert.ThrowsAsync<WabiSabiProtocolException>(async () => await handler.ConfirmConnectionAsync(req));
Assert.Equal(WabiSabiProtocolErrorCode.IncorrectRequestedAmountCredentials, ex.ErrorCode);
await arena.StopAsync(CancellationToken.None);

MODIFY
WalletWasabi.Tests/UnitTests/WabiSabi/Backend/RegisterOutputTests.cs
WalletWasabi.Tests/UnitTests/WabiSabi/Backend/RegisterOutputTests.cs
@@ -29,7 +29,7 @@ namespace WalletWasabi.Tests.UnitTests.WabiSabi.Backend
await using PostRequestHandler handler = new(cfg, new Prison(), arena, new MockRpcClient());
var req = WabiSabiFactory.CreateOutputRegistrationRequest(round);
- var resp = handler.RegisterOutput(req);
+ var resp = await handler.RegisterOutputAsync(req);
Assert.NotEmpty(round.Bobs);
Assert.NotNull(resp);
Assert.NotNull(resp.AmountCredentials);
@@ -45,7 +45,7 @@ namespace WalletWasabi.Tests.UnitTests.WabiSabi.Backend
using Arena arena = await WabiSabiFactory.CreateAndStartArenaAsync();
await using PostRequestHandler handler = new(new WabiSabiConfig(), new Prison(), arena, new MockRpcClient());
var req = WabiSabiFactory.CreateOutputRegistrationRequest();
- var ex = Assert.Throws<WabiSabiProtocolException>(() => handler.RegisterOutput(req));
+ var ex = await Assert.ThrowsAsync<WabiSabiProtocolException>(async () => await handler.RegisterOutputAsync(req));
Assert.Equal(WabiSabiProtocolErrorCode.RoundNotFound, ex.ErrorCode);
await arena.StopAsync(CancellationToken.None);
@@ -62,7 +62,7 @@ namespace WalletWasabi.Tests.UnitTests.WabiSabi.Backend
var req = WabiSabiFactory.CreateOutputRegistrationRequest(round, key.PubKey.GetAddress(ScriptPubKeyType.Legacy, Network.Main).ScriptPubKey);
await using PostRequestHandler handler = new(cfg, new Prison(), arena, new MockRpcClient());
- var ex = Assert.Throws<WabiSabiProtocolException>(() => handler.RegisterOutput(req));
+ var ex = await Assert.ThrowsAsync<WabiSabiProtocolException>(async () => await handler.RegisterOutputAsync(req));
Assert.Equal(WabiSabiProtocolErrorCode.ScriptNotAllowed, ex.ErrorCode);
await arena.StopAsync(CancellationToken.None);
@@ -80,7 +80,7 @@ namespace WalletWasabi.Tests.UnitTests.WabiSabi.Backend
var req = WabiSabiFactory.CreateOutputRegistrationRequest(round);
- var ex = Assert.Throws<WabiSabiProtocolException>(() => handler.RegisterOutput(req));
+ var ex = await Assert.ThrowsAsync<WabiSabiProtocolException>(async () => await handler.RegisterOutputAsync(req));
Assert.Equal(WabiSabiProtocolErrorCode.NotEnoughFunds, ex.ErrorCode);
await arena.StopAsync(CancellationToken.None);
@@ -98,7 +98,7 @@ namespace WalletWasabi.Tests.UnitTests.WabiSabi.Backend
var req = WabiSabiFactory.CreateOutputRegistrationRequest(round);
- var ex = Assert.Throws<WabiSabiProtocolException>(() => handler.RegisterOutput(req));
+ var ex = await Assert.ThrowsAsync<WabiSabiProtocolException>(async () => await handler.RegisterOutputAsync(req));
Assert.Equal(WabiSabiProtocolErrorCode.TooMuchFunds, ex.ErrorCode);
await arena.StopAsync(CancellationToken.None);
@@ -116,7 +116,7 @@ namespace WalletWasabi.Tests.UnitTests.WabiSabi.Backend
var req = WabiSabiFactory.CreateOutputRegistrationRequest(round, weight: 30);
- var ex = Assert.Throws<WabiSabiProtocolException>(() => handler.RegisterOutput(req));
+ var ex = await Assert.ThrowsAsync<WabiSabiProtocolException>(async () => await handler.RegisterOutputAsync(req));
Assert.Equal(WabiSabiProtocolErrorCode.IncorrectRequestedWeightCredentials, ex.ErrorCode);
await arena.StopAsync(CancellationToken.None);
@@ -137,7 +137,7 @@ namespace WalletWasabi.Tests.UnitTests.WabiSabi.Backend
{
var req = WabiSabiFactory.CreateOutputRegistrationRequest(round);
round.Phase = phase;
- var ex = Assert.Throws<WabiSabiProtocolException>(() => handler.RegisterOutput(req));
+ var ex = await Assert.ThrowsAsync<WabiSabiProtocolException>(async () => await handler.RegisterOutputAsync(req));
Assert.Equal(WabiSabiProtocolErrorCode.WrongPhase, ex.ErrorCode);
}
}

MODIFY
WalletWasabi.Tests/UnitTests/WabiSabi/Backend/RemoveInputTests.cs
WalletWasabi.Tests/UnitTests/WabiSabi/Backend/RemoveInputTests.cs
@@ -30,11 +30,11 @@ namespace WalletWasabi.Tests.UnitTests.WabiSabi.Backend
// There's no such alice yet, so success.
var req = new InputsRemovalRequest(round.Id, Guid.NewGuid());
- handler.RemoveInput(req);
+ await handler.RemoveInputAsync(req);
// There was the alice we want to remove so success.
req = new InputsRemovalRequest(round.Id, alice.Id);
- handler.RemoveInput(req);
+ await handler.RemoveInputAsync(req);
await arena.StopAsync(CancellationToken.None);
}
@@ -46,7 +46,7 @@ namespace WalletWasabi.Tests.UnitTests.WabiSabi.Backend
await using PostRequestHandler handler = new(new WabiSabiConfig(), new Prison(), arena, new MockRpcClient());
var req = new InputsRemovalRequest(Guid.NewGuid(), Guid.NewGuid());
- var ex = Assert.Throws<WabiSabiProtocolException>(() => handler.RemoveInput(req));
+ var ex = await Assert.ThrowsAsync<WabiSabiProtocolException>(async () => await handler.RemoveInputAsync(req));
Assert.Equal(WabiSabiProtocolErrorCode.RoundNotFound, ex.ErrorCode);
await arena.StopAsync(CancellationToken.None);
@@ -66,7 +66,7 @@ namespace WalletWasabi.Tests.UnitTests.WabiSabi.Backend
{
round.Phase = phase;
await using PostRequestHandler handler = new(cfg, new Prison(), arena, new MockRpcClient());
- var ex = Assert.Throws<WabiSabiProtocolException>(() => handler.RemoveInput(req));
+ var ex = await Assert.ThrowsAsync<WabiSabiProtocolException>(async () => await handler.RemoveInputAsync(req));
Assert.Equal(WabiSabiProtocolErrorCode.WrongPhase, ex.ErrorCode);
}
}

MODIFY
WalletWasabi.Tests/UnitTests/WabiSabi/Backend/SignTransactionTests.cs
WalletWasabi.Tests/UnitTests/WabiSabi/Backend/SignTransactionTests.cs
@@ -36,7 +36,7 @@ namespace WalletWasabi.Tests.UnitTests.WabiSabi.Backend
var req = new TransactionSignaturesRequest(round.Id, new[] { new InputWitnessPair(0, signedCoinJoin.Inputs[0].WitScript) });
await using PostRequestHandler handler = new(cfg, new Prison(), arena, new MockRpcClient());
- handler.SignTransaction(req);
+ await handler.SignTransactionAsync(req);
Assert.True(round.Coinjoin.Inputs.First().HasWitScript());
await arena.StopAsync(CancellationToken.None);
}
@@ -47,7 +47,7 @@ namespace WalletWasabi.Tests.UnitTests.WabiSabi.Backend
using Arena arena = await WabiSabiFactory.CreateAndStartArenaAsync();
await using PostRequestHandler handler = new(new WabiSabiConfig(), new Prison(), arena, new MockRpcClient());
var req = new TransactionSignaturesRequest(Guid.NewGuid(), null!);
- var ex = Assert.Throws<WabiSabiProtocolException>(() => handler.SignTransaction(req));
+ var ex = await Assert.ThrowsAsync<WabiSabiProtocolException>(async () => await handler.SignTransactionAsync(req));
Assert.Equal(WabiSabiProtocolErrorCode.RoundNotFound, ex.ErrorCode);
await arena.StopAsync(CancellationToken.None);
}
@@ -66,7 +66,7 @@ namespace WalletWasabi.Tests.UnitTests.WabiSabi.Backend
{
round.Phase = phase;
await using PostRequestHandler handler = new(cfg, new Prison(), arena, new MockRpcClient());
- var ex = Assert.Throws<WabiSabiProtocolException>(() => handler.SignTransaction(req));
+ var ex = await Assert.ThrowsAsync<WabiSabiProtocolException>(async () => await handler.SignTransactionAsync(req));
Assert.Equal(WabiSabiProtocolErrorCode.WrongPhase, ex.ErrorCode);
}
}
@@ -96,7 +96,7 @@ namespace WalletWasabi.Tests.UnitTests.WabiSabi.Backend
var req = new TransactionSignaturesRequest(round.Id, new[] { new InputWitnessPair(0, signedCoinJoin.Inputs[0].WitScript) });
await using PostRequestHandler handler = new(cfg, new Prison(), arena, new MockRpcClient());
- var ex = Assert.Throws<WabiSabiProtocolException>(() => handler.SignTransaction(req));
+ var ex = await Assert.ThrowsAsync<WabiSabiProtocolException>(async () => await handler.SignTransactionAsync(req));
Assert.Equal(WabiSabiProtocolErrorCode.WrongCoinjoinSignature, ex.ErrorCode);
await arena.StopAsync(CancellationToken.None);
}

MODIFY
WalletWasabi/WabiSabi/Backend/PostRequests/PostRequestHandler.cs
WalletWasabi/WabiSabi/Backend/PostRequests/PostRequestHandler.cs
@@ -41,33 +41,33 @@ namespace WalletWasabi.WabiSabi.Backend.PostRequests
{
Dictionary<Coin, byte[]> coinRoundSignaturePairs = await InputRegistrationHandler.PreProcessAsync(request, Prison, Rpc, Config).ConfigureAwait(false);
- return Arena.RegisterInput(
+ return await Arena.RegisterInputAsync(
request.RoundId,
coinRoundSignaturePairs,
request.ZeroAmountCredentialRequests,
- request.ZeroWeightCredentialRequests);
+ request.ZeroWeightCredentialRequests).ConfigureAwait(false);
}
}
- public void RemoveInput(InputsRemovalRequest request)
+ public async Task RemoveInputAsync(InputsRemovalRequest request)
{
DisposeGuard();
using (RunningTasks.RememberWith(RunningRequests))
{
- Arena.RemoveInput(request);
+ await Arena.RemoveInputAsync(request).ConfigureAwait(false);
}
}
- public ConnectionConfirmationResponse ConfirmConnection(ConnectionConfirmationRequest request)
+ public async Task<ConnectionConfirmationResponse> ConfirmConnectionAsync(ConnectionConfirmationRequest request)
{
DisposeGuard();
using (RunningTasks.RememberWith(RunningRequests))
{
- return Arena.ConfirmConnection(request);
+ return await Arena.ConfirmConnectionAsync(request).ConfigureAwait(false);
}
}
- public OutputRegistrationResponse RegisterOutput(OutputRegistrationRequest request)
+ public async Task<OutputRegistrationResponse> RegisterOutputAsync(OutputRegistrationRequest request)
{
DisposeGuard();
using (RunningTasks.RememberWith(RunningRequests))
@@ -77,16 +77,16 @@ namespace WalletWasabi.WabiSabi.Backend.PostRequests
throw new WabiSabiProtocolException(WabiSabiProtocolErrorCode.ScriptNotAllowed);
}
- return Arena.RegisterOutput(request);
+ return await Arena.RegisterOutputAsync(request).ConfigureAwait(false);
}
}
- public void SignTransaction(TransactionSignaturesRequest request)
+ public async Task SignTransactionAsync(TransactionSignaturesRequest request)
{
DisposeGuard();
using (RunningTasks.RememberWith(RunningRequests))
{
- Arena.SignTransaction(request);
+ await Arena.SignTransactionAsync(request).ConfigureAwait(false);
}
}

MODIFY
WalletWasabi/WabiSabi/Backend/Rounds/Arena.cs
WalletWasabi/WabiSabi/Backend/Rounds/Arena.cs
using NBitcoin;
using NBitcoin.RPC;
+using Nito.AsyncEx;
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
@@ -29,7 +30,7 @@ namespace WalletWasabi.WabiSabi.Backend.Rounds
}
public Dictionary<Guid, Round> Rounds { get; } = new();
- private object Lock { get; } = new();
+ private AsyncLock AsyncLock { get; } = new();
public Network Network { get; }
public WabiSabiConfig Config { get; }
public IRPCClient Rpc { get; }
@@ -37,21 +38,22 @@ namespace WalletWasabi.WabiSabi.Backend.Rounds
protected override async Task ActionAsync(CancellationToken cancel)
{
- var feeRate = (await Rpc.EstimateSmartFeeAsync((int)Config.ConfirmationTarget, EstimateSmartFeeMode.Conservative).ConfigureAwait(false)).FeeRate;
- lock (Lock)
+ using (await AsyncLock.LockAsync(cancel).ConfigureAwait(false))
{
// Remove timed out alices.
TimeoutAlices();
// Ensure there's at least one non-blame round in input registration.
- CreateRounds(feeRate);
+ await CreateRoundsAsync().ConfigureAwait(false);
}
}
- private void CreateRounds(FeeRate feeRate)
+ private async Task CreateRoundsAsync()
{
if (!Rounds.Values.Any(x => !x.IsBlameRound && x.Phase == Phase.InputRegistration))
{
+ var feeRate = (await Rpc.EstimateSmartFeeAsync((int)Config.ConfirmationTarget, EstimateSmartFeeMode.Conservative).ConfigureAwait(false)).FeeRate;
+
RoundParameters roundParams = new(Config, Network, Random, feeRate);
Round r = new(roundParams);
Rounds.Add(r.Id, r);
@@ -70,13 +72,13 @@ namespace WalletWasabi.WabiSabi.Backend.Rounds
}
}
- public InputsRegistrationResponse RegisterInput(
+ public async Task<InputsRegistrationResponse> RegisterInputAsync(
Guid roundId,
Dictionary<Coin, byte[]> coinRoundSignaturePairs,
ZeroCredentialsRequest zeroAmountCredentialRequests,
ZeroCredentialsRequest zeroWeightCredentialRequests)
{
- lock (Lock)
+ using (await AsyncLock.LockAsync().ConfigureAwait(false))
{
return InputRegistrationHandler.RegisterInput(
roundId,
@@ -88,9 +90,9 @@ namespace WalletWasabi.WabiSabi.Backend.Rounds
}
}
- public void RemoveInput(InputsRemovalRequest request)
+ public async Task RemoveInputAsync(InputsRemovalRequest request)
{
- lock (Lock)
+ using (await AsyncLock.LockAsync().ConfigureAwait(false))
{
if (!Rounds.TryGetValue(request.RoundId, out var round))
{
@@ -100,9 +102,9 @@ namespace WalletWasabi.WabiSabi.Backend.Rounds
}
}
- public ConnectionConfirmationResponse ConfirmConnection(ConnectionConfirmationRequest request)
+ public async Task<ConnectionConfirmationResponse> ConfirmConnectionAsync(ConnectionConfirmationRequest request)
{
- lock (Lock)
+ using (await AsyncLock.LockAsync().ConfigureAwait(false))
{
if (!Rounds.TryGetValue(request.RoundId, out var round))
{
@@ -155,9 +157,9 @@ namespace WalletWasabi.WabiSabi.Backend.Rounds
}
}
- public OutputRegistrationResponse RegisterOutput(OutputRegistrationRequest request)
+ public async Task<OutputRegistrationResponse> RegisterOutputAsync(OutputRegistrationRequest request)
{
- lock (Lock)
+ using (await AsyncLock.LockAsync().ConfigureAwait(false))
{
if (!Rounds.TryGetValue(request.RoundId, out var round))
{
@@ -201,9 +203,9 @@ namespace WalletWasabi.WabiSabi.Backend.Rounds
}
}
- public void SignTransaction(TransactionSignaturesRequest request)
+ public async Task SignTransactionAsync(TransactionSignaturesRequest request)
{
- lock (Lock)
+ using (await AsyncLock.LockAsync().ConfigureAwait(false))
{
if (!Rounds.TryGetValue(request.RoundId, out var round))
{

Turtles all the way down