Fix tests, move initialization code in a startup task
MODIFY
.gitignore
.gitignore
@@ -273,3 +273,4 @@ lcov.info
*.DS_Store
/WalletWasabi.Backend/wwwroot/onion-index.html
/NuGet.Config
+/WalletWasabi.Tests/wwwroot/unversioned

MODIFY
WalletWasabi.Backend/Config.cs
WalletWasabi.Backend/Config.cs
@@ -2,6 +2,7 @@ using NBitcoin;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System;
+using System.ComponentModel;
using System.IO;
using System.Net;
using System.Text;
@@ -44,6 +45,10 @@ namespace WalletWasabi.Backend
[JsonProperty(PropertyName = "RegTestBitcoinCorePort")]
public int? RegTestBitcoinCorePort { get; internal set; }
+ [DefaultValue(true)]
+ [JsonProperty(PropertyName = "ListenRoundConfigFileChanges", DefaultValueHandling = DefaultValueHandling.Populate)]
+ public bool ListenRoundConfigFileChanges { get; set; }
+
private EndPoint _bitcoinCoreEndPoint;
public EndPoint GetBitcoinCoreEndPoint()
@@ -101,7 +106,8 @@ namespace WalletWasabi.Backend
string regTestBitcoinCoreHost,
int? mainNetBitcoinCorePort,
int? testNetBitcoinCorePort,
- int? regTestBitcoinCorePort)
+ int? regTestBitcoinCorePort,
+ bool? listenRoundConfigFileChanges)
{
Network = Guard.NotNull(nameof(network), network);
BitcoinRpcConnectionString = Guard.NotNullOrEmptyOrWhitespace(nameof(bitcoinRpcConnectionString), bitcoinRpcConnectionString);
@@ -112,6 +118,7 @@ namespace WalletWasabi.Backend
MainNetBitcoinCorePort = Guard.NotNull(nameof(mainNetBitcoinCorePort), mainNetBitcoinCorePort);
TestNetBitcoinCorePort = Guard.NotNull(nameof(testNetBitcoinCorePort), testNetBitcoinCorePort);
RegTestBitcoinCorePort = Guard.NotNull(nameof(regTestBitcoinCorePort), regTestBitcoinCorePort);
+ ListenRoundConfigFileChanges = listenRoundConfigFileChanges.HasValue ? listenRoundConfigFileChanges.Value : true;
}
/// <inheritdoc />
@@ -139,7 +146,7 @@ namespace WalletWasabi.Backend
MainNetBitcoinCorePort = Network.Main.DefaultPort;
TestNetBitcoinCorePort = Network.TestNet.DefaultPort;
RegTestBitcoinCorePort = Network.RegTest.DefaultPort;
-
+ ListenRoundConfigFileChanges = true;
if (!File.Exists(FilePath))
{
Logger.LogInfo<Config>($"{nameof(Config)} file did not exist. Created at path: `{FilePath}`.");
@@ -158,6 +165,7 @@ namespace WalletWasabi.Backend
MainNetBitcoinCorePort = config.MainNetBitcoinCorePort ?? MainNetBitcoinCorePort;
TestNetBitcoinCorePort = config.TestNetBitcoinCorePort ?? TestNetBitcoinCorePort;
RegTestBitcoinCorePort = config.RegTestBitcoinCorePort ?? RegTestBitcoinCorePort;
+ ListenRoundConfigFileChanges = config.ListenRoundConfigFileChanges;
}
await ToFileAsync();
@@ -176,7 +184,6 @@ namespace WalletWasabi.Backend
string jsonString = await File.ReadAllTextAsync(FilePath, Encoding.UTF8);
var newConfig = JsonConvert.DeserializeObject<JObject>(jsonString);
var currentConfig = JObject.FromObject(this);
-
return !JToken.DeepEquals(newConfig, currentConfig);
}

MODIFY
WalletWasabi.Backend/Controllers/BatchController.cs
WalletWasabi.Backend/Controllers/BatchController.cs
@@ -19,22 +19,19 @@ namespace WalletWasabi.Backend.Controllers
[Route("api/v" + Helpers.Constants.BackendMajorVersion + "/btc/[controller]")]
public class BatchController : Controller
{
- public BatchController(Global backendGlobal)
- {
- Global = backendGlobal;
- }
public Global Global { get; }
public BlockchainController BlockchainController { get; }
public ChaumianCoinJoinController ChaumianCoinJoinController { get; }
public HomeController HomeController { get; }
public OffchainController OffchainController { get; }
- public BatchController(BlockchainController blockchainController, ChaumianCoinJoinController chaumianCoinJoinController, HomeController homeController, OffchainController offchainController)
+ public BatchController(BlockchainController blockchainController, ChaumianCoinJoinController chaumianCoinJoinController, HomeController homeController, OffchainController offchainController, Global backendGlobal)
{
BlockchainController = blockchainController;
ChaumianCoinJoinController = chaumianCoinJoinController;
HomeController = homeController;
OffchainController = offchainController;
+ Global = backendGlobal;
}
[HttpGet("synchronize")]

MODIFY
WalletWasabi.Backend/Controllers/BlockchainController.cs
WalletWasabi.Backend/Controllers/BlockchainController.cs
@@ -23,19 +23,15 @@ namespace WalletWasabi.Backend.Controllers
public class BlockchainController : Controller
{
private IMemoryCache Cache { get; }
-
- BlockchainController(Global backendGlobal)
- {
- Global = backendGlobal;
- }
public Global Global { get; }
private RPCClient RpcClient => Global.RpcClient;
private Network Network => Global.Config.Network;
- public BlockchainController(IMemoryCache memoryCache)
+ public BlockchainController(IMemoryCache memoryCache, Global backendGlobal)
{
Cache = memoryCache;
+ Global = backendGlobal;
}
/// <summary>

ADD
WalletWasabi.Backend/Extensions.cs
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.Extensions.DependencyInjection;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace WalletWasabi.Backend
+{
+ public static class Extensions
+ {
+ public static IServiceCollection AddStartupTask<T>(this IServiceCollection services)
+ where T : class, IStartupTask
+ => services.AddTransient<IStartupTask, T>();
+ public static async Task RunWithTasksAsync(this IWebHost webHost, CancellationToken cancellationToken = default)
+ {
+ // Load all tasks from DI
+ var startupTasks = webHost.Services.GetServices<IStartupTask>();
+
+ // Execute all the tasks
+ foreach (var startupTask in startupTasks)
+ {
+ await startupTask.ExecuteAsync(cancellationToken);
+ }
+
+ // Start the tasks as normal
+ await webHost.RunAsync(cancellationToken);
+ }
+ }
+}

MODIFY
WalletWasabi.Backend/Global.cs
WalletWasabi.Backend/Global.cs
@@ -62,7 +62,7 @@ namespace WalletWasabi.Backend
await Coordinator.MakeSureTwoRunningRoundsAsync();
Logger.LogInfo<CcjCoordinator>("Chaumian CoinJoin Coordinator is successfully initialized and started two new rounds.");
- if (roundConfig.FilePath != null)
+ if (config.ListenRoundConfigFileChanges && roundConfig.FilePath != null)
{
RoundConfigWatcher = new ConfigWatcher(RoundConfig);
RoundConfigWatcher.Start(TimeSpan.FromSeconds(10), () =>

ADD
WalletWasabi.Backend/IStartupTask.cs
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace WalletWasabi.Backend
+{
+ public interface IStartupTask
+ {
+ Task ExecuteAsync(CancellationToken cancellationToken = default);
+ }
+}

ADD
WalletWasabi.Backend/InitConfigStartupTask.cs
+using Microsoft.AspNetCore.Hosting.Internal;
+using Microsoft.Extensions.Hosting;
+using NBitcoin;
+using NBitcoin.RPC;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using WalletWasabi.Logging;
+using WalletWasabi.Models.ChaumianCoinJoin;
+
+namespace WalletWasabi.Backend
+{
+ public class InitConfigStartupTask : IStartupTask
+ {
+ private readonly Global _backendGlobal;
+ private readonly HostingEnvironment _hostingEnvironment;
+ private UnversionedWebBuilder _unversionedWebBuilder;
+
+ private static List<string> Last5CoinJoins { get; set; } = new List<string>();
+ private static object UpdateUnversionedLock { get; } = new object();
+
+ public InitConfigStartupTask(Global backendGlobal, IHostingEnvironment hostingEnvironment)
+ {
+ _backendGlobal = backendGlobal;
+ _hostingEnvironment = (HostingEnvironment)hostingEnvironment;
+ }
+
+ public async Task ExecuteAsync(CancellationToken cancellationToken)
+ {
+ Logger.InitializeDefaults(Path.Combine(_backendGlobal.DataDir, "Logs.txt"));
+ Logger.LogStarting("Wasabi Backend");
+
+ AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException;
+ TaskScheduler.UnobservedTaskException += TaskScheduler_UnobservedTaskException;
+ var configFilePath = Path.Combine(_backendGlobal.DataDir, "Config.json");
+ var config = new Config(configFilePath);
+ await config.LoadOrCreateDefaultFileAsync();
+ Logger.LogInfo<Config>("Config is successfully initialized.");
+
+ var roundConfigFilePath = Path.Combine(_backendGlobal.DataDir, "CcjRoundConfig.json");
+ var roundConfig = new CcjRoundConfig(roundConfigFilePath);
+ await roundConfig.LoadOrCreateDefaultFileAsync();
+ Logger.LogInfo<CcjRoundConfig>("RoundConfig is successfully initialized.");
+
+ var rpc = new RPCClient(
+ credentials: RPCCredentialString.Parse(config.BitcoinRpcConnectionString),
+ network: config.Network);
+
+ await _backendGlobal.InitializeAsync(config, roundConfig, rpc);
+
+ try
+ {
+ _unversionedWebBuilder = new UnversionedWebBuilder(_hostingEnvironment.WebRootPath);
+ Directory.CreateDirectory(_unversionedWebBuilder.UnversionedFolder);
+ _unversionedWebBuilder.CreateDownloadTextWithVersionHtml();
+ _unversionedWebBuilder.CloneAndUpdateOnionIndexHtml();
+
+ if (File.Exists(_backendGlobal.Coordinator.CoinJoinsFilePath))
+ {
+ string[] allLines = File.ReadAllLines(_backendGlobal.Coordinator.CoinJoinsFilePath);
+ Last5CoinJoins = allLines.TakeLast(5).Reverse().ToList();
+ _unversionedWebBuilder.UpdateCoinJoinsHtml(_backendGlobal, Last5CoinJoins);
+ }
+
+ _backendGlobal.Coordinator.CoinJoinBroadcasted += Coordinator_CoinJoinBroadcasted;
+ }
+ catch (Exception ex)
+ {
+ Logger.LogWarning(ex, nameof(Program));
+ }
+ }
+
+ private static void TaskScheduler_UnobservedTaskException(object sender, UnobservedTaskExceptionEventArgs e)
+ {
+ Logger.LogWarning(e?.Exception, "UnobservedTaskException");
+ }
+
+ private static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
+ {
+ Logger.LogWarning(e?.ExceptionObject as Exception, "UnhandledException");
+ }
+
+ private void Coordinator_CoinJoinBroadcasted(object sender, Transaction tx)
+ {
+ try
+ {
+ lock (UpdateUnversionedLock)
+ {
+ if (Last5CoinJoins.Count > 4)
+ {
+ Last5CoinJoins.RemoveLast();
+ }
+ Last5CoinJoins.Insert(0, tx.GetHash().ToString());
+ _unversionedWebBuilder.UpdateCoinJoinsHtml(_backendGlobal, Last5CoinJoins);
+ }
+ }
+ catch (Exception ex)
+ {
+ Logger.LogWarning(ex, nameof(Program));
+ }
+ }
+ }
+}

MODIFY
WalletWasabi.Backend/Program.cs
WalletWasabi.Backend/Program.cs
@@ -15,9 +15,6 @@ namespace WalletWasabi.Backend
{
public class Program
{
- private static List<string> Last5CoinJoins { get; set; } = new List<string>();
- private static object UpdateUnversionedLock { get; } = new object();
-
#pragma warning disable IDE1006 // Naming Styles
public static async Task Main(string[] args)
@@ -25,48 +22,6 @@ namespace WalletWasabi.Backend
{
try
{
- var backendGlobal = new Global();
- Logger.InitializeDefaults(Path.Combine(backendGlobal.DataDir, "Logs.txt"));
- Logger.LogStarting("Wasabi Backend");
-
- AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException;
- TaskScheduler.UnobservedTaskException += TaskScheduler_UnobservedTaskException;
- var configFilePath = Path.Combine(backendGlobal.DataDir, "Config.json");
- var config = new Config(configFilePath);
- await config.LoadOrCreateDefaultFileAsync();
- Logger.LogInfo<Config>("Config is successfully initialized.");
-
- var roundConfigFilePath = Path.Combine(backendGlobal.DataDir, "CcjRoundConfig.json");
- var roundConfig = new CcjRoundConfig(roundConfigFilePath);
- await roundConfig.LoadOrCreateDefaultFileAsync();
- Logger.LogInfo<CcjRoundConfig>("RoundConfig is successfully initialized.");
-
- var rpc = new RPCClient(
- credentials: RPCCredentialString.Parse(config.BitcoinRpcConnectionString),
- network: config.Network);
-
- await backendGlobal.InitializeAsync(config, roundConfig, rpc);
-
- try
- {
- Directory.CreateDirectory(UnversionedWebBuilder.UnversionedFolder);
- UnversionedWebBuilder.CreateDownloadTextWithVersionHtml();
- UnversionedWebBuilder.CloneAndUpdateOnionIndexHtml();
-
- if (File.Exists(backendGlobal.Coordinator.CoinJoinsFilePath))
- {
- string[] allLines = File.ReadAllLines(backendGlobal.Coordinator.CoinJoinsFilePath);
- Last5CoinJoins = allLines.TakeLast(5).Reverse().ToList();
- UnversionedWebBuilder.UpdateCoinJoinsHtml(backendGlobal, Last5CoinJoins);
- }
-
- backendGlobal.Coordinator.CoinJoinBroadcasted += (s, a) => Coordinator_CoinJoinBroadcasted(s, a, backendGlobal);
- }
- catch (Exception ex)
- {
- Logger.LogWarning(ex, nameof(Program));
- }
-
var endPoint = "http://localhost:37127/";
using (var host = WebHost.CreateDefaultBuilder(args)
@@ -74,44 +29,13 @@ namespace WalletWasabi.Backend
.UseUrls(endPoint)
.Build())
{
- await host.RunAsync();
+ await host.RunWithTasksAsync();
}
}
catch (Exception ex)
{
Logger.LogCritical<Program>(ex);
}
- // Note: Do not do finally here. Dispose in Startup.cs.
- }
-
- private static void TaskScheduler_UnobservedTaskException(object sender, UnobservedTaskExceptionEventArgs e)
- {
- Logger.LogWarning(e?.Exception, "UnobservedTaskException");
- }
-
- private static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
- {
- Logger.LogWarning(e?.ExceptionObject as Exception, "UnhandledException");
- }
-
- private static void Coordinator_CoinJoinBroadcasted(object sender, Transaction tx, Global backendGlobal)
- {
- try
- {
- lock (UpdateUnversionedLock)
- {
- if (Last5CoinJoins.Count > 4)
- {
- Last5CoinJoins.RemoveLast();
- }
- Last5CoinJoins.Insert(0, tx.GetHash().ToString());
- UnversionedWebBuilder.UpdateCoinJoinsHtml(backendGlobal, Last5CoinJoins);
- }
- }
- catch (Exception ex)
- {
- Logger.LogWarning(ex, nameof(Program));
- }
}
}
}

MODIFY
WalletWasabi.Backend/Startup.cs
WalletWasabi.Backend/Startup.cs
@@ -50,7 +50,7 @@ namespace WalletWasabi.Backend
services.AddSingleton<IExchangeRateProvider>(new ExchangeRateProvider());
services.AddSingleton<Global>(new Global());
-
+ services.AddStartupTask<InitConfigStartupTask>();
services.AddResponseCompression();
}

MODIFY
WalletWasabi.Backend/UnversionedWebBuilder.cs
WalletWasabi.Backend/UnversionedWebBuilder.cs
@@ -6,20 +6,23 @@ using System.Linq;
namespace WalletWasabi.Backend
{
- public static class UnversionedWebBuilder
+ public class UnversionedWebBuilder
{
- public static string DebugRootFolder { get; } = Path.GetFullPath(Path.Combine(AppContext.BaseDirectory, "..\\..\\..\\wwwroot"));
- public static string PublishedRootFolder { get; } = Path.GetFullPath(Path.Combine(AppContext.BaseDirectory, "wwwroot"));
+ public UnversionedWebBuilder(string rootFolder)
+ {
+ RootFolder = rootFolder;
+ UnversionedFolder = Path.GetFullPath(Path.Combine(RootFolder, "unversioned"));
+ }
- public static string RootFolder { get; } = Directory.Exists(PublishedRootFolder) ? PublishedRootFolder : DebugRootFolder;
+ public string RootFolder { get; }
- public static string UnversionedFolder { get; } = Path.GetFullPath(Path.Combine(RootFolder, "unversioned"));
+ public string UnversionedFolder { get; }
- public static string CreateFilePath(string fileName) => Path.Combine(UnversionedFolder, fileName);
+ public string CreateFilePath(string fileName) => Path.Combine(UnversionedFolder, fileName);
public static string HtmlStartLine { get; } = "<link href=\"../css/bootstrap.css\" rel=\"stylesheet\" type=\"text/css\" />\r\n<link href=\"../css/OpenSansCondensed300700.css\" rel=\"stylesheet\" type=\"text/css\" />\r\n";
- public static void CreateDownloadTextWithVersionHtml()
+ public void CreateDownloadTextWithVersionHtml()
{
var filePath = CreateFilePath("download-text-with-version.html");
var content = HtmlStartLine + $"<h1 class=\"text-center\">Download Wasabi Wallet {Helpers.Constants.ClientVersion}</h1>";
@@ -27,7 +30,7 @@ namespace WalletWasabi.Backend
File.WriteAllText(filePath, content);
}
- public static void CloneAndUpdateOnionIndexHtml()
+ public void CloneAndUpdateOnionIndexHtml()
{
var path = Path.Combine(RootFolder, "index.html");
var onionPath = Path.Combine(RootFolder, "onion-index.html");
@@ -41,7 +44,7 @@ namespace WalletWasabi.Backend
File.WriteAllText(onionPath, content);
}
- public static void UpdateCoinJoinsHtml(Global backendGlobal, IEnumerable<string> coinJoins)
+ public void UpdateCoinJoinsHtml(Global backendGlobal, IEnumerable<string> coinJoins)
{
var filePath = CreateFilePath("coinjoins-table.html");
var onionFilePath = CreateFilePath("onion-coinjoins-table.html");

MODIFY
WalletWasabi.Tests/NodeBuilding/CoreNode.cs
WalletWasabi.Tests/NodeBuilding/CoreNode.cs
@@ -62,7 +62,7 @@ namespace WalletWasabi.Tests.NodeBuilding
private int[] Ports { get; }
- private readonly NetworkCredential Creds;
+ internal readonly NetworkCredential Creds;
public RPCClient CreateRpcClient()
{

MODIFY
WalletWasabi.Tests/NodeBuilding/NodeBuilder.cs
WalletWasabi.Tests/NodeBuilding/NodeBuilder.cs
@@ -146,7 +146,6 @@ namespace WalletWasabi.Tests.NodeBuilding
var rpcPassword = config["regtest.rpcpassword"];
var pidFileName = config["regtest.pid"];
var credentials = new NetworkCredential(rpcUser, rpcPassword);
-
try
{
var rpc = new RPCClient(credentials, new Uri("http://127.0.0.1:" + rpcPort + "/"), Network.RegTest);
@@ -216,6 +215,7 @@ namespace WalletWasabi.Tests.NodeBuilding
}
private List<IDisposable> Disposables { get; } = new List<IDisposable>();
+ public Network Network => NBitcoin.Network.RegTest;
internal void AddDisposable(IDisposable group)
{

MODIFY
WalletWasabi.Tests/RegTests.cs
WalletWasabi.Tests/RegTests.cs
@@ -74,7 +74,7 @@ namespace WalletWasabi.Tests
private async Task<(string password, RPCClient rpc, Network network, CcjCoordinator coordinator, ServiceConfiguration serviceConfiguration, BitcoinStore bitcoinStore, Backend.Global backendGlobal)> InitializeTestEnvironmentAsync(int numberOfBlocksToGenerate, [CallerMemberName] string caller = null)
{
- var global = new Backend.Global();
+ var global = RegTestFixture.Global;
await AssertFiltersInitializedAsync(global); // Make sure fitlers are created on the server side.
if (numberOfBlocksToGenerate != 0)
{

MODIFY
WalletWasabi.Tests/XunitConfiguration/RegTestFixture.cs
WalletWasabi.Tests/XunitConfiguration/RegTestFixture.cs
@@ -2,6 +2,7 @@ using Microsoft.AspNetCore;
using Microsoft.AspNetCore.Hosting;
using NBitcoin;
using System;
+using System.IO;
using System.Net;
using System.Threading;
using System.Threading.Tasks;
@@ -31,27 +32,35 @@ namespace WalletWasabi.Tests.XunitConfiguration
BackendRegTestNode = BackendNodeBuilder.Nodes[0];
var rpc = BackendRegTestNode.CreateRpcClient();
-
- var config = new Config(rpc.Network, rpc.Authentication, IPAddress.Loopback.ToString(), IPAddress.Loopback.ToString(), BackendRegTestNode.Endpoint.Address.ToString(), Network.Main.DefaultPort, Network.TestNet.DefaultPort, BackendRegTestNode.Endpoint.Port);
+ var connectionString = new NBitcoin.RPC.RPCCredentialString()
+ {
+ Server = rpc.Address.AbsoluteUri,
+ UserPassword = BackendRegTestNode.Creds
+ }.ToString();
+ var backendGlobal = new Backend.Global();
+ var config = new Config(BackendNodeBuilder.Network, connectionString, IPAddress.Loopback.ToString(), IPAddress.Loopback.ToString(), BackendRegTestNode.Endpoint.Address.ToString(), Network.Main.DefaultPort, Network.TestNet.DefaultPort, BackendRegTestNode.Endpoint.Port, false);
+ var configFilePath = Path.Combine(backendGlobal.DataDir, "Config.json");
+ config.SetFilePath(configFilePath);
+ config.ToFileAsync().GetAwaiter().GetResult();
var roundConfig = CreateRoundConfig(Money.Coins(0.1m), Constants.OneDayConfirmationTarget, 0.7, 0.1m, 100, 120, 60, 60, 60, 1, 24, true, 11);
-
- var backendGlobal = new Backend.Global();
- backendGlobal.InitializeAsync(config, roundConfig, rpc).GetAwaiter().GetResult();
+ var roundConfigFilePath = Path.Combine(backendGlobal.DataDir, "CcjRoundConfig.json");
+ roundConfig.SetFilePath(roundConfigFilePath);
+ roundConfig.ToFileAsync().GetAwaiter().GetResult();
BackendEndPoint = $"http://localhost:{new Random().Next(37130, 38000)}/";
BackendHost = WebHost.CreateDefaultBuilder()
.UseStartup<Startup>()
+ .UseWebRoot("../../../../WalletWasabi.Backend/wwwroot")
.UseUrls(BackendEndPoint)
.Build();
-
- var hostInitializationTask = BackendHost.RunAsync();
+ Global = (Backend.Global)BackendHost.Services.GetService(typeof(Backend.Global));
+ var hostInitializationTask = BackendHost.RunWithTasksAsync();
Logger.LogInfo($"Started Backend webhost: {BackendEndPoint}", nameof(Global));
var delayTask = Task.Delay(3000);
Task.WaitAny(delayTask, hostInitializationTask); // Wait for server to initialize (Without this OSX CI will fail)
}
-
public static CcjRoundConfig CreateRoundConfig(Money denomination,
int confirmationTarget,
double confirmationTargetReductionRate,
@@ -82,7 +91,7 @@ namespace WalletWasabi.Tests.XunitConfiguration
MaximumMixingLevelCount = maximumMixingLevelCount,
};
}
-
+ public Backend.Global Global { get; set; }
public void Dispose()
{
// Cleanup tests...

Fix tests, move initialization code in a startup task