Add isolateStream as parameter too.
MODIFY
WalletWasabi.Tests/IntegrationTests/LiveServerTests.cs
WalletWasabi.Tests/IntegrationTests/LiveServerTests.cs
@@ -175,7 +175,7 @@ namespace WalletWasabi.Tests.IntegrationTests
private TorHttpClient MakeTorHttpClient(NetworkType networkType)
{
Uri baseUri = LiveServerTestsFixture.UriMappings[networkType];
- return new TorHttpClient(baseUri, Common.TorSocks5Endpoint, isolateStream: false);
+ return new TorHttpClient(baseUri, Common.TorSocks5Endpoint);
}
}
}

MODIFY
WalletWasabi.Tests/UnitTests/Clients/MockTorHttpClient.cs
WalletWasabi.Tests/UnitTests/Clients/MockTorHttpClient.cs
@@ -17,7 +17,7 @@ namespace WalletWasabi.Tests.UnitTests.Clients
public async Task<HttpResponseMessage> SendAsync(HttpMethod method, string relativeUri, HttpContent? content = null, CancellationToken cancel = default)
{
string body = (content is { })
- ? await content.ReadAsStringAsync().ConfigureAwait(false)
+ ? await content.ReadAsStringAsync(cancel).ConfigureAwait(false)
: "";
// It does not matter which URI is actually used here, we just need to construct absolute URI to be able to access `uri.Query`.
@@ -28,10 +28,15 @@ namespace WalletWasabi.Tests.UnitTests.Clients
return await OnSendAsync(method, uri.AbsolutePath, parameters, body).ConfigureAwait(false);
}
- public async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancel = default)
+ public Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken token = default)
+ {
+ return SendAsync(request, isolateStream: false, token);
+ }
+
+ public async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, bool isolateStream = false, CancellationToken token = default)
{
string body = (request.Content is { })
- ? await request.Content.ReadAsStringAsync().ConfigureAwait(false)
+ ? await request.Content.ReadAsStringAsync(token).ConfigureAwait(false)
: "";
Uri uri = request.RequestUri!;

MODIFY
WalletWasabi/Tor/Http/ClearnetHttpClient.cs
WalletWasabi/Tor/Http/ClearnetHttpClient.cs
@@ -34,6 +34,11 @@ namespace WalletWasabi.Tor.Http
/// <inheritdoc cref="HttpClient.SendAsync(HttpRequestMessage, CancellationToken)"/>
public Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken token = default)
+ {
+ return SendAsync(request, isolateStream: false, token);
+ }
+
+ public Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, bool isolateStream = false, CancellationToken token = default)
{
return HttpClient.SendAsync(request, token);
}

MODIFY
WalletWasabi/Tor/Http/IHttpClient.cs
WalletWasabi/Tor/Http/IHttpClient.cs
@@ -14,10 +14,19 @@ namespace WalletWasabi.Tor.Http
/// <summary>TLS protocols we support for both clearnet and Tor proxy.</summary>
static readonly SslProtocols SupportedSslProtocols = SslProtocols.Tls | SslProtocols.Tls11 | SslProtocols.Tls12;
- /// <inheritdoc cref="HttpClient.SendAsync(HttpRequestMessage, CancellationToken)"/>
- Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken token = default);
+ Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken token = default)
+ {
+ return SendAsync(request, isolateStream: false, token);
+ }
+
+ Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, bool isolateStream = false, CancellationToken token = default);
+
+ Task<HttpResponseMessage> SendAsync(HttpMethod method, Uri requestUri, HttpContent? content = null, CancellationToken token = default)
+ {
+ return SendAsync(method, requestUri, content, isolateStream: false, token);
+ }
- async Task<HttpResponseMessage> SendAsync(HttpMethod method, Uri requestUri, HttpContent? content = null, CancellationToken token = default)
+ async Task<HttpResponseMessage> SendAsync(HttpMethod method, Uri requestUri, HttpContent? content = null, bool isolateStream = false, CancellationToken token = default)
{
using var httpRequestMessage = new HttpRequestMessage(method, requestUri);

MODIFY
WalletWasabi/Tor/Http/TorHttpClient.cs
WalletWasabi/Tor/Http/TorHttpClient.cs
@@ -37,7 +37,7 @@ namespace WalletWasabi.Tor.Http
// Connecting to loopback's URIs cannot be done via Tor.
TorSocks5EndPoint = DestinationUriAction().IsLoopback ? null : torSocks5EndPoint;
TorSocks5Client = null;
- IsolateStream = isolateStream;
+ ForceIsolateStream = isolateStream;
}
public static DateTimeOffset? TorDoesntWorkSince
@@ -61,15 +61,16 @@ namespace WalletWasabi.Tor.Http
public Func<Uri> DestinationUriAction { get; }
public EndPoint? TorSocks5EndPoint { get; private set; }
- private bool IsolateStream { get; }
+ /// <summary>All HTTP(s) requests sent by this HTTP client must use different Tor circuits.</summary>
+ private bool ForceIsolateStream { get; }
private TorSocks5Client? TorSocks5Client { get; set; }
private static AsyncLock AsyncLock { get; } = new AsyncLock(); // We make everything synchronous, so slow, but at least stable.
- private Task<HttpResponseMessage> ClearnetRequestAsync(HttpRequestMessage request, CancellationToken cancellationToken = default)
+ private Task<HttpResponseMessage> ClearnetRequestAsync(HttpRequestMessage request, bool isolateStream, CancellationToken cancellationToken = default)
{
- return new ClearnetHttpClient(DestinationUriAction).SendAsync(request, cancellationToken);
+ return new ClearnetHttpClient(DestinationUriAction).SendAsync(request, isolateStream, cancellationToken);
}
/// <remarks>
@@ -90,15 +91,15 @@ namespace WalletWasabi.Tor.Http
// Use clearnet HTTP client when Tor is disabled.
if (TorSocks5EndPoint is null)
{
- return await ClearnetRequestAsync(request, cancel).ConfigureAwait(false);
+ return await ClearnetRequestAsync(request, ForceIsolateStream, cancel).ConfigureAwait(false);
}
else
{
- return await TorRequestAsync(request, cancel).ConfigureAwait(false);
+ return await TorRequestAsync(request, ForceIsolateStream, cancel).ConfigureAwait(false);
}
}
- private async Task<HttpResponseMessage> TorRequestAsync(HttpRequestMessage request, CancellationToken cancel)
+ private async Task<HttpResponseMessage> TorRequestAsync(HttpRequestMessage request, bool isolateStream, CancellationToken cancel)
{
try
{
@@ -106,7 +107,7 @@ namespace WalletWasabi.Tor.Http
{
try
{
- HttpResponseMessage ret = await SendAsync(request, cancel).ConfigureAwait(false);
+ HttpResponseMessage ret = await SendAsync(request, isolateStream, cancel).ConfigureAwait(false);
TorDoesntWorkSince = null;
return ret;
}
@@ -120,7 +121,7 @@ namespace WalletWasabi.Tor.Http
cancel.ThrowIfCancellationRequested();
try
{
- HttpResponseMessage ret2 = await SendAsync(request, cancel).ConfigureAwait(false);
+ HttpResponseMessage ret2 = await SendAsync(request, isolateStream, cancel).ConfigureAwait(false);
TorDoesntWorkSince = null;
return ret2;
}
@@ -148,7 +149,7 @@ namespace WalletWasabi.Tor.Http
cancel.ThrowIfCancellationRequested();
- HttpResponseMessage ret3 = await SendAsync(request, cancel).ConfigureAwait(false);
+ HttpResponseMessage ret3 = await SendAsync(request, isolateStream, cancel).ConfigureAwait(false);
TorDoesntWorkSince = null;
return ret3;
}
@@ -173,20 +174,26 @@ namespace WalletWasabi.Tor.Http
}
/// <exception cref="OperationCanceledException">If <paramref name="cancel"/> is set.</exception>
- public async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancel = default)
+ public Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancel = default)
+ {
+ return SendAsync(request, ForceIsolateStream, cancel);
+ }
+
+ /// <exception cref="OperationCanceledException">If <paramref name="token"/> is set.</exception>
+ public async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, bool isolateStream = false, CancellationToken token = default)
{
// Use clearnet HTTP client when Tor is disabled.
if (TorSocks5EndPoint is null)
{
- return await ClearnetRequestAsync(request, cancel).ConfigureAwait(false);
+ return await ClearnetRequestAsync(request, ForceIsolateStream || isolateStream, token).ConfigureAwait(false);
}
else
{
- return await TorRequestCoreAsync(request, cancel).ConfigureAwait(false);
+ return await TorRequestCoreAsync(request, ForceIsolateStream || isolateStream, token).ConfigureAwait(false);
}
}
- private async Task<HttpResponseMessage> TorRequestCoreAsync(HttpRequestMessage request, CancellationToken cancel)
+ private async Task<HttpResponseMessage> TorRequestCoreAsync(HttpRequestMessage request, bool isolateStream, CancellationToken cancel)
{
// https://tools.ietf.org/html/rfc7230#section-2.7.1
// A sender MUST NOT generate an "http" URI with an empty host identifier.
@@ -209,7 +216,7 @@ namespace WalletWasabi.Tor.Http
{
TorSocks5Client = new TorSocks5Client(TorSocks5EndPoint!);
await TorSocks5Client.ConnectAsync().ConfigureAwait(false);
- await TorSocks5Client.HandshakeAsync(IsolateStream, cancel).ConfigureAwait(false);
+ await TorSocks5Client.HandshakeAsync(isolateStream, cancel).ConfigureAwait(false);
await TorSocks5Client.ConnectToDestinationAsync(host, request.RequestUri.Port, cancel).ConfigureAwait(false);
if (request.RequestUri.Scheme == "https")

Add isolateStream as parameter too.