Create unit test to verify behavior
MODIFY
WalletWasabi.Tests/UnitTests/WabiSabi/Integration/WabiSabiHttpApiIntegrationTests.cs
WalletWasabi.Tests/UnitTests/WabiSabi/Integration/WabiSabiHttpApiIntegrationTests.cs
@@ -10,6 +10,7 @@ using System.Threading;
using System.Threading.Tasks;
using WalletWasabi.BitcoinCore.Rpc;
using WalletWasabi.Blockchain.Keys;
+using WalletWasabi.Blockchain.TransactionOutputs;
using WalletWasabi.Tests.Helpers;
using WalletWasabi.Tor.Http;
using WalletWasabi.Tor.Socks5.Pool.Circuits;
@@ -59,10 +60,9 @@ public class WabiSabiHttpApiIntegrationTests : IClassFixture<WabiSabiApiApplicat
using var signingKey = new Key();
var ownershipProof = WabiSabiFactory.CreateOwnershipProof(signingKey, round.Id);
- var ex = await Assert.ThrowsAsync<HttpRequestException>(async () =>
+ var wex = await Assert.ThrowsAsync<WabiSabiProtocolException>(async () =>
await apiClient.RegisterInputAsync(round.Id, nonExistingOutPoint, ownershipProof, CancellationToken.None));
- var wex = Assert.IsType<WabiSabiProtocolException>(ex.InnerException);
Assert.Equal(WabiSabiProtocolErrorCode.InputSpent, wex.ErrorCode);
}
@@ -89,10 +89,9 @@ public class WabiSabiHttpApiIntegrationTests : IClassFixture<WabiSabiApiApplicat
using var signingKey = new Key();
var ownershipProof = WabiSabiFactory.CreateOwnershipProof(signingKey, round.Id);
- var ex = await Assert.ThrowsAsync<HttpRequestException>(async () =>
+ var wex = await Assert.ThrowsAsync<WabiSabiProtocolException>(async () =>
await apiClient.RegisterInputAsync(round.Id, bannedOutPoint, ownershipProof, timeoutCts.Token));
- var wex = Assert.IsType<WabiSabiProtocolException>(ex.InnerException);
Assert.Equal(WabiSabiProtocolErrorCode.InputLongBanned, wex.ErrorCode);
var inputBannedData = Assert.IsType<InputBannedExceptionData>(wex.ExceptionData);
Assert.True(inputBannedData.BannedUntil > DateTimeOffset.UtcNow);
@@ -118,20 +117,8 @@ public class WabiSabiHttpApiIntegrationTests : IClassFixture<WabiSabiApiApplicat
_output.WriteLine("Coins were created successfully");
var httpClient = _apiApplicationFactory.WithWebHostBuilder(builder =>
- builder.ConfigureServices(services =>
- {
- var rpc = BitcoinFactory.GetMockMinimalRpc();
-
- // Make the coordinator to believe that the coins are real and
- // that they exist in the blockchain with many confirmations.
- rpc.OnGetTxOutAsync = (txId, idx, _) => new()
+ builder.AddMockRpcClient(coins, rpc =>
{
- Confirmations = 101,
- IsCoinBase = false,
- ScriptPubKeyType = "witness_v0_keyhash",
- TxOut = coins.Single(x => x.TransactionId == txId && x.Index == idx).TxOut
- };
-
// Make the coordinator believe that the transaction is being
// broadcasted using the RPC interface. Once we receive this tx
// (the `SendRawTransationAsync` was invoked) we stop waiting
@@ -141,22 +128,9 @@ public class WabiSabiHttpApiIntegrationTests : IClassFixture<WabiSabiApiApplicat
transactionCompleted.SetResult(tx);
return tx.GetHash();
};
-
- rpc.OnGetRawTransactionAsync = (txid, throwIfNotFound) =>
+ })
+ .ConfigureServices(services =>
{
- var tx = coins.First(coin => coin.TransactionId == txid)?.Transaction?.Transaction;
-
- if (tx is null)
- {
- return Task.FromException<Transaction>(new InvalidOperationException("tx not found"));
- }
-
- return Task.FromResult(tx);
- };
-
- // Instruct the coordinator DI container to use these two scoped
- // services to build everything (WabiSabi controller, arena, etc)
- services.AddScoped<IRPCClient>(s => rpc);
services.AddScoped(s => new WabiSabiConfig
{
MaxInputCountByRound = inputCount,
@@ -231,47 +205,12 @@ public class WabiSabiHttpApiIntegrationTests : IClassFixture<WabiSabiApiApplicat
.ToImmutableArray();
var httpClient = _apiApplicationFactory.WithWebHostBuilder(builder =>
- builder.ConfigureServices(services =>
+ builder
+ .AddMockRpcClient(coins, rpc => { })
+ .ConfigureServices(services =>
{
- var rpc = BitcoinFactory.GetMockMinimalRpc();
-
- // Make the coordinator to believe that the coins are real and
- // that they exist in the blockchain with many confirmations.
- rpc.OnGetTxOutAsync = (txId, idx, _) => new()
- {
- Confirmations = 101,
- IsCoinBase = false,
- ScriptPubKeyType = "witness_v0_keyhash",
- TxOut = coins.Single(x => x.TransactionId == txId && x.Index == idx).TxOut
- };
-
- // Make the coordinator believe that the transaction is being
- // broadcasted using the RPC interface. Once we receive this tx
- // (the `SendRawTransationAsync` was invoked) we stop waiting
- // and finish the waiting tasks to finish the test successfully.
- rpc.OnSendRawTransactionAsync = (tx) =>
- {
- transactionCompleted.SetResult(tx);
- return tx.GetHash();
- };
-
- // Simulates that all the transactions that created our coins
- // really exists in the blockchain.
- rpc.OnGetRawTransactionAsync = (txid, throwIfNotFound) =>
- {
- var tx = coins.First(coin => coin.TransactionId == txid)?.Transaction?.Transaction;
-
- if (tx is null)
- {
- return Task.FromException<Transaction>(new InvalidOperationException("tx not found"));
- }
-
- return Task.FromResult(tx);
- };
-
// Instruct the coordinator DI container to use these two scoped
// services to build everything (WabiSabi controller, arena, etc)
- services.AddScoped<IRPCClient>(s => rpc);
services.AddScoped(s => new WabiSabiConfig
{
MaxInputCountByRound = inputCount,
@@ -280,9 +219,7 @@ public class WabiSabiHttpApiIntegrationTests : IClassFixture<WabiSabiApiApplicat
OutputRegistrationTimeout = TimeSpan.FromSeconds(60),
TransactionSigningTimeout = TimeSpan.FromSeconds(60),
MaxSuggestedAmountBase = Money.Satoshis(ProtocolConstants.MaxAmountPerAlice)
- }
-
- );
+ });
// Emulate that the all our outputs had been already used in the past.
// the server will prevent the registration and fail with an WabiSabiProtocolError.
@@ -375,20 +312,8 @@ public class WabiSabiHttpApiIntegrationTests : IClassFixture<WabiSabiApiApplicat
.ToArray();
var httpClient = _apiApplicationFactory.WithWebHostBuilder(builder =>
- builder.ConfigureServices(services =>
+ builder.AddMockRpcClient(Enumerable.Concat(coins, badCoins).ToArray(), rpc =>
{
- var rpc = BitcoinFactory.GetMockMinimalRpc();
-
- // Make the coordinator to believe that the coins are real and
- // that they exist in the blockchain with many confirmations.
- rpc.OnGetTxOutAsync = (txId, idx, _) => new()
- {
- Confirmations = 101,
- IsCoinBase = false,
- ScriptPubKeyType = "witness_v0_keyhash",
- TxOut = Enumerable.Concat(coins, badCoins).Single(x => x.TransactionId == txId && x.Index == idx).TxOut
- };
-
rpc.OnGetRawTransactionAsync = (txid, throwIfNotFound) =>
{
var tx = Transaction.Create(Network.Main);
@@ -404,10 +329,9 @@ public class WabiSabiHttpApiIntegrationTests : IClassFixture<WabiSabiApiApplicat
transactionCompleted.SetResult(tx);
return tx.GetHash();
};
-
- // Instruct the coordinator DI container to use these two scoped
- // services to build everything (WabiSabi controller, arena, etc)
- services.AddScoped<IRPCClient>(s => rpc);
+ })
+ .ConfigureServices(services =>
+ {
services.AddScoped<WabiSabiConfig>(s => new WabiSabiConfig
{
MaxInputCountByRound = 2 * inputCount,
@@ -620,6 +544,46 @@ public class WabiSabiHttpApiIntegrationTests : IClassFixture<WabiSabiApiApplicat
}
}
+public static class IWebHostBuilderExtensions
+{
+ public static IWebHostBuilder AddMockRpcClient(this IWebHostBuilder builder, SmartCoin[] coins, Action<MockRpcClient> options)
+ {
+ var rpc = BitcoinFactory.GetMockMinimalRpc();
+
+ // Make the coordinator to believe that the coins are real and
+ // that they exist in the blockchain with many confirmations.
+ rpc.OnGetTxOutAsync = (txId, idx, _) => new()
+ {
+ Confirmations = 101,
+ IsCoinBase = false,
+ ScriptPubKeyType = "witness_v0_keyhash",
+ TxOut = coins.Single(x => x.TransactionId == txId && x.Index == idx).TxOut
+ };
+
+ rpc.OnGetRawTransactionAsync = (txid, throwIfNotFound) =>
+ {
+ var tx = coins.First(coin => coin.TransactionId == txid)?.Transaction?.Transaction;
+
+ if (tx is null)
+ {
+ return Task.FromException<Transaction>(new InvalidOperationException("tx not found"));
+ }
+
+ return Task.FromResult(tx);
+ };
+
+ options(rpc);
+
+ builder.ConfigureServices(services =>
+ {
+ // Instruct the coordinator DI container to use these two scoped
+ // services to build everything (WabiSabi controller, arena, etc)
+ services.AddScoped<IRPCClient>(s => rpc);
+ });
+ return builder;
+ }
+}
+
public class StuttererHttpClient : HttpClientWrapper
{
public StuttererHttpClient(HttpClient httpClient) : base(httpClient)

MODIFY
WalletWasabi/Tor/Http/Extensions/HttpResponseMessageExtensions.cs
WalletWasabi/Tor/Http/Extensions/HttpResponseMessageExtensions.cs
@@ -59,6 +59,18 @@ public static class HttpResponseMessageExtensions
return response;
}
+ public static async Task ThrowUnwrapExceptionFromContentAsync(this HttpResponseMessage me, CancellationToken cancellationToken = default)
+ {
+ try
+ {
+ await me.ThrowRequestExceptionFromContentAsync(cancellationToken).ConfigureAwait(false);
+ }
+ catch (Exception e) when (e.InnerException is {} innerException)
+ {
+ throw innerException;
+ }
+ }
+
public static async Task ThrowRequestExceptionFromContentAsync(this HttpResponseMessage me, CancellationToken cancellationToken = default)
{
var errorMessage = "";

MODIFY
WalletWasabi/WabiSabi/Client/CoinJoinClient.cs
WalletWasabi/WabiSabi/Client/CoinJoinClient.cs
@@ -12,6 +12,7 @@ using WalletWasabi.Extensions;
using WalletWasabi.Helpers;
using WalletWasabi.Logging;
using WalletWasabi.Tor.Socks5.Pool.Circuits;
+using WalletWasabi.WabiSabi.Backend.Models;
using WalletWasabi.WabiSabi.Backend.Rounds;
using WalletWasabi.WabiSabi.Client.CoinJoinProgressEvents;
using WalletWasabi.WabiSabi.Client.CredentialDependencies;
@@ -280,7 +281,7 @@ public class CoinJoinClient
return (aliceClient, personCircuit);
}
- catch (HttpRequestException)
+ catch (WabiSabiProtocolException)
{
personCircuit?.Dispose();
return (null, null);

MODIFY
WalletWasabi/WabiSabi/Client/WabiSabiHttpApiClient.cs
WalletWasabi/WabiSabi/Client/WabiSabiHttpApiClient.cs
@@ -122,7 +122,7 @@ public class WabiSabiHttpApiClient : IWabiSabiApiRequestHandler
if (!response.IsSuccessStatusCode)
{
- await response.ThrowRequestExceptionFromContentAsync(cancellationToken).ConfigureAwait(false);
+ await response.ThrowUnwrapExceptionFromContentAsync(cancellationToken).ConfigureAwait(false);
}
return await response.Content.ReadAsStringAsync(cancellationToken).ConfigureAwait(false);

Create unit test to verify behavior