Reuse code across microservices.
ADD
WalletWasabi.Tests/NodeBuilding/BitcoindException.cs
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace WalletWasabi.Tests.NodeBuilding
+{
+ public class BitcoindException : Exception
+ {
+ public BitcoindException(string message) : base(message)
+ {
+ }
+ }
+}

ADD
WalletWasabi.Tests/NodeBuilding/BitcoindProcessBridge.cs
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.IO;
+using System.Runtime.InteropServices;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+using WalletWasabi.Helpers;
+using WalletWasabi.Interfaces;
+using WalletWasabi.Logging;
+using WalletWasabi.Microservices;
+
+namespace WalletWasabi.Tests.NodeBuilding
+{
+ public class BitcoindProcessBridge : ProcessBridge
+ {
+ public BitcoindProcessBridge() : base(EnvironmentHelpers.GetBinaryPath("BitcoinCore", "bitcoind"))
+ {
+ }
+ }
+}

MODIFY
WalletWasabi.Tests/NodeBuilding/CoreNode.cs
WalletWasabi.Tests/NodeBuilding/CoreNode.cs
@@ -22,7 +22,7 @@ namespace WalletWasabi.Tests.NodeBuilding
public EndPoint P2pEndPoint { get; private set; }
public EndPoint RpcEndPoint { get; private set; }
public RPCClient RpcClient { get; private set; }
-
+ private BitcoindProcessBridge Bridge { get; set; }
public Process Process { get; private set; }
public string DataDir { get; private set; }
@@ -124,7 +124,8 @@ namespace WalletWasabi.Tests.NodeBuilding
await File.WriteAllTextAsync(configPath, config.ToString());
using (await coreNode.KillerLock.LockAsync())
{
- coreNode.Process = Process.Start(new FileInfo(EnvironmentHelpers.GetBinaryPath("BitcoinCore", "bitcoind")).FullName, "-conf=bitcoin.conf" + " -datadir=" + coreNode.DataDir + " -debug=1");
+ coreNode.Bridge = new BitcoindProcessBridge();
+ coreNode.Process = coreNode.Bridge.Start($"-conf=bitcoin.conf -datadir={coreNode.DataDir} -debug=1", false);
string pidFile = Path.Combine(coreNode.DataDir, "regtest", "bitcoind.pid");
if (!File.Exists(pidFile))
{
@@ -151,6 +152,22 @@ namespace WalletWasabi.Tests.NodeBuilding
return coreNode;
}
+ public static async Task<Version> GetVersionAsync(CancellationToken cancel)
+ {
+ var arguments = "-version";
+ var bridge = new BitcoindProcessBridge();
+ var (responseString, exitCode) = await bridge.SendCommandAsync(arguments, false, cancel).ConfigureAwait(false);
+
+ if (exitCode != 0)
+ {
+ throw new BitcoindException($"'bitcoind {arguments}' exited with incorrect exit code: {exitCode}.");
+ }
+ var firstLine = responseString.Split(new char[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries).First();
+ var versionString = firstLine.TrimStart("Bitcoin Core Daemon version v", StringComparison.OrdinalIgnoreCase);
+ var version = new Version(versionString);
+ return version;
+ }
+
public async Task<Node> CreateP2pNodeAsync()
{
return await Node.ConnectAsync(Network.RegTest, P2pEndPoint);

MODIFY
WalletWasabi.Tests/UnitTests/BitcoinCore/NodeBuildingTests.cs
WalletWasabi.Tests/UnitTests/BitcoinCore/NodeBuildingTests.cs
@@ -2,6 +2,7 @@ using NBitcoin;
using System;
using System.Collections.Generic;
using System.Text;
+using System.Threading;
using System.Threading.Tasks;
using WalletWasabi.Tests.NodeBuilding;
using Xunit;
@@ -75,5 +76,13 @@ namespace WalletWasabi.Tests.UnitTests.BitcoinCore
await coreNode.StopAsync();
}
}
+
+ [Fact]
+ public async Task GetVersionTestsAsync()
+ {
+ using var cts = new CancellationTokenSource(7000);
+ Version version = await CoreNode.GetVersionAsync(cts.Token);
+ Assert.Equal(Helpers.Constants.BitcoinCoreVersion, version);
+ }
}
}

MODIFY
WalletWasabi.Tests/UnitTests/Hwi/DefaultResponseTests.cs
WalletWasabi.Tests/UnitTests/Hwi/DefaultResponseTests.cs
@@ -46,7 +46,7 @@ namespace WalletWasabi.Tests.UnitTests.Hwi
{
using var cts = new CancellationTokenSource(ReasonableRequestTimeout);
Version version = await client.GetVersionAsync(cts.Token);
- Assert.Equal(new Version("1.0.3"), version);
+ Assert.Equal(Helpers.Constants.HwiVersion, version);
}
[Theory]

MODIFY
WalletWasabi.Tests/UnitTests/Hwi/HwiProcessBridgeMock.cs
WalletWasabi.Tests/UnitTests/Hwi/HwiProcessBridgeMock.cs
using System;
using System.Collections.Generic;
+using System.Diagnostics;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using WalletWasabi.Hwi.Models;
using WalletWasabi.Hwi.Parsers;
-using WalletWasabi.Interfaces;
+using WalletWasabi.Microservices;
namespace WalletWasabi.Tests.UnitTests.Hwi
{
@@ -261,5 +262,10 @@ namespace WalletWasabi.Tests.UnitTests.Hwi
return extPubKey != null;
}
+
+ public Process Start(string arguments, bool openConsole)
+ {
+ throw new NotImplementedException();
+ }
}
}

MODIFY
WalletWasabi/Helpers/Constants.cs
WalletWasabi/Helpers/Constants.cs
@@ -10,6 +10,8 @@ namespace WalletWasabi.Helpers
{
public static readonly Version ClientVersion = new Version(1, 1, 9, 3);
public const string BackendMajorVersion = "3";
+ public static readonly Version HwiVersion = new Version("1.0.3");
+ public static readonly Version BitcoinCoreVersion = new Version("0.18.1");
public const uint ProtocolVersionWitnessVersion = 70012;

MODIFY
WalletWasabi/Hwi/HwiClient.cs
WalletWasabi/Hwi/HwiClient.cs
@@ -15,8 +15,8 @@ using WalletWasabi.Hwi.Exceptions;
using WalletWasabi.Hwi.Models;
using WalletWasabi.Hwi.Parsers;
using WalletWasabi.Hwi.ProcessBridge;
-using WalletWasabi.Interfaces;
using WalletWasabi.KeyManagement;
+using WalletWasabi.Microservices;
namespace WalletWasabi.Hwi
{

MODIFY
WalletWasabi/Hwi/ProcessBridge/HwiProcessBridge.cs
WalletWasabi/Hwi/ProcessBridge/HwiProcessBridge.cs
@@ -9,81 +9,35 @@ using System.Threading.Tasks;
using WalletWasabi.Helpers;
using WalletWasabi.Interfaces;
using WalletWasabi.Logging;
+using WalletWasabi.Microservices;
namespace WalletWasabi.Hwi.ProcessBridge
{
- public class HwiProcessBridge : IProcessBridge
+ public class HwiProcessBridge : Microservices.ProcessBridge
{
- public async Task<(string response, int exitCode)> SendCommandAsync(string arguments, bool openConsole, CancellationToken cancel)
+ public HwiProcessBridge() : base(EnvironmentHelpers.GetBinaryPath("Hwi", "hwi"))
{
- string responseString;
- int exitCode;
- string hwiPath = EnvironmentHelpers.GetBinaryPath("Hwi", "hwi");
-
- var fileName = hwiPath;
- var finalArguments = arguments;
- var redirectStandardOutput = !openConsole;
- var useShellExecute = openConsole;
- var createNoWindow = !openConsole;
- var windowStyle = openConsole ? ProcessWindowStyle.Normal : ProcessWindowStyle.Hidden;
-
- if (openConsole && !RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
- {
- throw new PlatformNotSupportedException($"{RuntimeInformation.OSDescription} is not supported.");
- //var escapedArguments = (hwiPath + " " + arguments).Replace("\"", "\\\"");
- //useShellExecute = false;
- //if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
- //{
- // fileName = "xterm";
- // finalArguments = $"-e \"{escapedArguments}\"";
- //}
- //else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
- //{
- // fileName = "osascript";
- // finalArguments = $"-e 'tell application \"Terminal\" to do script \"{escapedArguments}\"'";
- //}
}
- ProcessStartInfo startInfo = new ProcessStartInfo
+ public new async Task<(string response, int exitCode)> SendCommandAsync(string arguments, bool openConsole, CancellationToken cancel)
{
- FileName = fileName,
- Arguments = finalArguments,
- RedirectStandardOutput = redirectStandardOutput,
- UseShellExecute = useShellExecute,
- CreateNoWindow = createNoWindow,
- WindowStyle = windowStyle
- };
+ var redirectStandardOutput = !openConsole;
- try
- {
- using var process = Process.Start(startInfo);
- await process.WaitForExitAsync(cancel).ConfigureAwait(false);
+ var (responseString, exitCode) = await base.SendCommandAsync(arguments, openConsole, cancel).ConfigureAwait(false);
- exitCode = process.ExitCode;
+ string modifiedResponseString;
if (redirectStandardOutput)
{
- responseString = await process.StandardOutput.ReadToEndAsync().ConfigureAwait(false);
+ modifiedResponseString = responseString;
}
else
{
- responseString = exitCode == 0
+ modifiedResponseString = exitCode == 0
? "{\"success\":\"true\"}"
: $"{{\"success\":\"false\",\"error\":\"Process terminated with exit code: {exitCode}.\"}}";
}
- }
- catch
- {
- Logger.LogInfo($"{nameof(startInfo.FileName)}: {startInfo.FileName}");
- Logger.LogInfo($"{nameof(startInfo.Arguments)}: {startInfo.Arguments}");
- Logger.LogInfo($"{nameof(startInfo.RedirectStandardOutput)}: {startInfo.RedirectStandardOutput}");
- Logger.LogInfo($"{nameof(startInfo.UseShellExecute)}: {startInfo.UseShellExecute}");
- Logger.LogInfo($"{nameof(startInfo.CreateNoWindow)}: {startInfo.CreateNoWindow}");
- Logger.LogInfo($"{nameof(startInfo.WindowStyle)}: {startInfo.WindowStyle}");
-
- throw;
- }
- return (responseString, exitCode);
+ return (modifiedResponseString, exitCode);
}
}
}

RENAME
WalletWasabi/Interfaces/IProcessBridge.cs
WalletWasabi/Microservices/IProcessBridge.cs
using System;
using System.Collections.Generic;
+using System.Diagnostics;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
-namespace WalletWasabi.Interfaces
+namespace WalletWasabi.Microservices
{
public interface IProcessBridge
{
Task<(string response, int exitCode)> SendCommandAsync(string arguments, bool openConsole, CancellationToken cancel);
+
+ Process Start(string arguments, bool openConsole);
}
}

ADD
WalletWasabi/Microservices/ProcessBridge.cs
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.IO;
+using System.Runtime.InteropServices;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+using WalletWasabi.Helpers;
+using WalletWasabi.Logging;
+
+namespace WalletWasabi.Microservices
+{
+ public class ProcessBridge : IProcessBridge
+ {
+ public string ProcessPath { get; }
+
+ public ProcessBridge(string processPath)
+ {
+ ProcessPath = Guard.NotNullOrEmptyOrWhitespace(nameof(processPath), processPath);
+ if (!File.Exists(ProcessPath))
+ {
+ var fileNameWithoutExtension = Path.GetFileNameWithoutExtension(ProcessPath);
+ throw new FileNotFoundException($"{fileNameWithoutExtension} is not found.", ProcessPath);
+ }
+ }
+
+ public Process Start(string arguments, bool openConsole)
+ {
+ var finalArguments = arguments;
+ var redirectStandardOutput = !openConsole;
+ var useShellExecute = openConsole;
+ var createNoWindow = !openConsole;
+ var windowStyle = openConsole ? ProcessWindowStyle.Normal : ProcessWindowStyle.Hidden;
+
+ if (openConsole && !RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
+ {
+ throw new PlatformNotSupportedException($"{RuntimeInformation.OSDescription} is not supported.");
+ //var escapedArguments = (hwiPath + " " + arguments).Replace("\"", "\\\"");
+ //useShellExecute = false;
+ //if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
+ //{
+ // fileName = "xterm";
+ // finalArguments = $"-e \"{escapedArguments}\"";
+ //}
+ //else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
+ //{
+ // fileName = "osascript";
+ // finalArguments = $"-e 'tell application \"Terminal\" to do script \"{escapedArguments}\"'";
+ //}
+ }
+
+ ProcessStartInfo startInfo = new ProcessStartInfo
+ {
+ FileName = ProcessPath,
+ Arguments = finalArguments,
+ RedirectStandardOutput = redirectStandardOutput,
+ UseShellExecute = useShellExecute,
+ CreateNoWindow = createNoWindow,
+ WindowStyle = windowStyle
+ };
+
+ try
+ {
+ return Process.Start(startInfo);
+ }
+ catch
+ {
+ Logger.LogInfo($"{nameof(startInfo.FileName)}: {startInfo.FileName}");
+ Logger.LogInfo($"{nameof(startInfo.Arguments)}: {startInfo.Arguments}");
+ Logger.LogInfo($"{nameof(startInfo.RedirectStandardOutput)}: {startInfo.RedirectStandardOutput}");
+ Logger.LogInfo($"{nameof(startInfo.UseShellExecute)}: {startInfo.UseShellExecute}");
+ Logger.LogInfo($"{nameof(startInfo.CreateNoWindow)}: {startInfo.CreateNoWindow}");
+ Logger.LogInfo($"{nameof(startInfo.WindowStyle)}: {startInfo.WindowStyle}");
+
+ throw;
+ }
+ }
+
+ public async Task<(string response, int exitCode)> SendCommandAsync(string arguments, bool openConsole, CancellationToken cancel)
+ {
+ string responseString;
+ int exitCode;
+
+ using var process = Start(arguments, openConsole);
+ await process.WaitForExitAsync(cancel).ConfigureAwait(false);
+
+ exitCode = process.ExitCode;
+ if (!openConsole)
+ {
+ responseString = await process.StandardOutput.ReadToEndAsync().ConfigureAwait(false);
+ }
+ else
+ {
+ responseString = string.Empty;
+ }
+
+ return (responseString, exitCode);
+ }
+ }
+}

Reuse code across microservices.