Add status monitor
MODIFY
WalletWasabi.Gui/Controls/StatusBar.xaml
WalletWasabi.Gui/Controls/StatusBar.xaml
<converters:ShouldDisplayValueConverter x:Key="ShouldDisplayValueConverter" />
<converters:PascalToPhraseConverter x:Key="PascalToPhraseConverter" />
<converters:StatusBarStatusStringConverter x:Key="StatusBarStatusStringConverter" />
+ <converters:RpcStatusStringConverter x:Key="RpcStatusStringConverter" />
</UserControl.Resources>
<Grid Cursor="{Binding UpdateAvailable, Converter={StaticResource ShowCursorConverter}}" Background="{Binding UpdateStatus, Converter={StaticResource UpdateStatusBrushConverter}}">
<i:Interaction.Behaviors>
</Grid>
<TextBlock Text="{Binding Tor, StringFormat=Tor is \{0\}, Converter={StaticResource PascalToPhraseConverter}}" Foreground="{Binding Tor, ConverterParameter=Tor, Converter={StaticResource StatusColorConverter}}" />
</StackPanel>
+ <Panel IsVisible="{Binding UseBitcoinCore}">
+ <StackPanel Orientation="Horizontal" Spacing="4" IsVisible="{Binding !CriticalUpdateAvailable}">
+ <Grid Height="10" Width="10">
+ <DrawingPresenter VerticalAlignment="Center" HorizontalAlignment="Center" Drawing="{DynamicResource StatusBar_ServerGlobe}" />
+ </Grid>
+ <TextBlock Text="{Binding BitcoinCoreStatus, Converter={StaticResource RpcStatusStringConverter}}" Foreground="{Binding BitcoinCoreStatus, ConverterParameter=BitcoinCoreStatus, Converter={StaticResource StatusColorConverter}}" />
+ </StackPanel>
+ </Panel>
<StackPanel Orientation="Horizontal" Spacing="4" IsVisible="{Binding !CriticalUpdateAvailable}">
<Grid Height="10" Width="10">
<DrawingPresenter VerticalAlignment="Center" HorizontalAlignment="Center" Drawing="{DynamicResource StatusBar_ServerGlobe}" />

ADD
WalletWasabi.Gui/Converters/RpcStatusStringConverter.cs
+using Avalonia.Data.Converters;
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Text;
+using WalletWasabi.BitcoinCore.Monitor;
+using WalletWasabi.Exceptions;
+using WalletWasabi.Helpers;
+
+namespace WalletWasabi.Gui.Converters
+{
+ public class RpcStatusStringConverter : IValueConverter
+ {
+ public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ if (value is RpcStatus val)
+ {
+ return Guard.Correct(val?.ToString());
+ }
+ else
+ {
+ throw new TypeArgumentException(value, typeof(Version), nameof(value));
+ }
+ }
+
+ public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ throw new NotImplementedException();
+ }
+ }
+}

MODIFY
WalletWasabi.Gui/Converters/StatusColorConverter.cs
WalletWasabi.Gui/Converters/StatusColorConverter.cs
@@ -5,6 +5,7 @@ using System;
using System.Collections.Generic;
using System.Globalization;
using System.Text;
+using WalletWasabi.BitcoinCore.Monitor;
using WalletWasabi.Models;
namespace WalletWasabi.Gui.Converters
@@ -15,6 +16,7 @@ namespace WalletWasabi.Gui.Converters
{
switch (parameter?.ToString())
{
+ case "BitcoinCoreStatus" when !(value as RpcStatus).Synchronized:
case "Tor" when Enum.Parse<TorStatus>(value.ToString()) != TorStatus.Running:
case "Backend" when Enum.Parse<BackendStatus>(value.ToString()) == BackendStatus.NotConnected:
case "Peers" when (int)value == 0:

MODIFY
WalletWasabi.Gui/Global.cs
WalletWasabi.Gui/Global.cs
@@ -19,6 +19,7 @@ using System.Threading;
using System.Threading.Tasks;
using WalletWasabi.BitcoinCore;
using WalletWasabi.BitcoinCore.Endpointing;
+using WalletWasabi.BitcoinCore.Monitor;
using WalletWasabi.CoinJoin.Client.Clients;
using WalletWasabi.Coins;
using WalletWasabi.Crypto;
@@ -64,6 +65,8 @@ namespace WalletWasabi.Gui
public TorProcessManager TorManager { get; private set; }
public CoreNode BitcoinCoreNode { get; private set; }
+ public RpcMonitor RpcMonitor { get; private set; }
+
public bool KillRequested { get; private set; } = false;
public UiConfig UiConfig { get; private set; }
@@ -80,6 +83,7 @@ namespace WalletWasabi.Gui
Directory.CreateDirectory(DataDir);
Directory.CreateDirectory(WalletsDir);
Directory.CreateDirectory(WalletBackupsDir);
+ RpcMonitor = new RpcMonitor();
}
public void InitializeUiConfig(UiConfig uiConfig)
@@ -227,6 +231,8 @@ namespace WalletWasabi.Gui
txIndex: null,
prune: null))
.ConfigureAwait(false);
+
+ RpcMonitor.Start(BitcoinCoreNode.RpcClient, TimeSpan.FromSeconds(3));
}
}
catch (Exception ex)
@@ -647,6 +653,12 @@ namespace WalletWasabi.Gui
Logger.LogInfo($"{nameof(RegTestMempoolServingNode)} is disposed.");
}
+ if (RpcMonitor != null)
+ {
+ await RpcMonitor.StopAsync();
+ Logger.LogInfo("Stopped monitoring RPC.");
+ }
+
if (Config.StopLocalBitcoinCoreOnShutdown && BitcoinCoreNode != null)
{
await BitcoinCoreNode.TryStopAsync().ConfigureAwait(false);

MODIFY
WalletWasabi.Gui/ViewModels/StatusBarViewModel.cs
WalletWasabi.Gui/ViewModels/StatusBarViewModel.cs
@@ -15,6 +15,7 @@ using System.Reactive.Linq;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
+using WalletWasabi.BitcoinCore.Monitor;
using WalletWasabi.Gui.Converters;
using WalletWasabi.Gui.Dialogs;
using WalletWasabi.Gui.Models;
@@ -46,7 +47,9 @@ namespace WalletWasabi.Gui.ViewModels
private UpdateStatus _updateStatus;
private bool _updateAvailable;
private bool _criticalUpdateAvailable;
+ private bool _useBitcoinCore;
private BackendStatus _backend;
+ private ObservableAsPropertyHelper<RpcStatus> _bitcoinCoreStatus;
private TorStatus _tor;
private int _peers;
private ObservableAsPropertyHelper<int> _filtersLeft;
@@ -76,6 +79,7 @@ namespace WalletWasabi.Gui.ViewModels
Synchronizer = synchronizer;
HashChain = synchronizer.BitcoinStore.HashChain;
UseTor = Global.Config.UseTor; // Do not make it dynamic, because if you change this config settings only next time will it activate.
+ UseBitcoinCore = Global.Config.StartLocalBitcoinCoreOnStartup;
_status = ActiveStatuses.WhenAnyValue(x => x.CurrentStatus)
.ObserveOn(RxApp.MainThreadScheduler)
@@ -118,6 +122,13 @@ namespace WalletWasabi.Gui.ViewModels
.Subscribe(usd => BtcPrice = $"${(long)usd}")
.DisposeWith(Disposables);
+ _bitcoinCoreStatus = Global.RpcMonitor
+ .WhenAnyValue(x => x.Status)
+ .Throttle(TimeSpan.FromMilliseconds(100))
+ .ObserveOn(RxApp.MainThreadScheduler)
+ .ToProperty(this, x => x.BitcoinCoreStatus)
+ .DisposeWith(Disposables);
+
Observable.FromEventPattern<bool>(Synchronizer, nameof(Synchronizer.ResponseArrivedIsGenSocksServFail))
.ObserveOn(RxApp.MainThreadScheduler)
.Subscribe(e => OnResponseArrivedIsGenSocksServFail(e.EventArgs))
@@ -242,6 +253,12 @@ namespace WalletWasabi.Gui.ViewModels
set => this.RaiseAndSetIfChanged(ref _criticalUpdateAvailable, value);
}
+ public bool UseBitcoinCore
+ {
+ get => _useBitcoinCore;
+ set => this.RaiseAndSetIfChanged(ref _useBitcoinCore, value);
+ }
+
public BackendStatus Backend
{
get => _backend;
@@ -261,6 +278,7 @@ namespace WalletWasabi.Gui.ViewModels
}
public int FiltersLeft => _filtersLeft?.Value ?? 0;
+ public RpcStatus BitcoinCoreStatus => _bitcoinCoreStatus?.Value ?? RpcStatus.Unresponsive;
public string BtcPrice
{

ADD
WalletWasabi/BitcoinCore/Monitor/RpcMonitor.cs
+using NBitcoin.RPC;
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+using WalletWasabi.Bases;
+using WalletWasabi.Helpers;
+using WalletWasabi.Logging;
+
+namespace WalletWasabi.BitcoinCore.Monitor
+{
+ public class RpcMonitor : NotifyPropertyChangedBase
+ {
+ /// <summary>
+ /// 0: Not started, 1: Running, 2: Stopping, 3: Stopped
+ /// </summary>
+ private long _running;
+
+ public bool IsRunning => Interlocked.Read(ref _running) == 1;
+
+ private CancellationTokenSource Stop { get; set; }
+
+ private RpcStatus _status;
+
+ public RpcStatus Status
+ {
+ get => _status;
+ private set => RaiseAndSetIfChanged(ref _status, value);
+ }
+
+ public RpcMonitor()
+ {
+ _running = 0;
+ Stop = new CancellationTokenSource();
+ }
+
+ public void Start(RPCClient rpc, TimeSpan period)
+ {
+ rpc = Guard.NotNull(nameof(rpc), rpc);
+ if (Interlocked.CompareExchange(ref _running, 1, 0) != 0)
+ {
+ return;
+ }
+
+ Task.Run(async () =>
+ {
+ try
+ {
+ while (IsRunning)
+ {
+ try
+ {
+ var bci = await rpc.GetBlockchainInfoAsync().ConfigureAwait(false);
+ var pi = await rpc.GetPeersInfoAsync().ConfigureAwait(false);
+
+ Status = RpcStatus.Responsive(bci.Headers, bci.Blocks, pi.Length);
+ }
+ catch (Exception ex)
+ {
+ Status = RpcStatus.Unresponsive;
+ Logger.LogError(ex);
+ }
+ finally
+ {
+ await Task.Delay(period, Stop.Token);
+ }
+ }
+ }
+ finally
+ {
+ Interlocked.CompareExchange(ref _running, 3, 2); // If IsStopping, make it stopped.
+ }
+ });
+ }
+
+ public async Task StopAsync()
+ {
+ Interlocked.CompareExchange(ref _running, 2, 1); // If running, make it stopping.
+ Stop?.Cancel();
+ while (Interlocked.CompareExchange(ref _running, 3, 0) == 2)
+ {
+ await Task.Delay(50);
+ }
+ Stop?.Dispose();
+ Stop = null;
+ }
+ }
+}

ADD
WalletWasabi/BitcoinCore/Monitor/RpcStatus.cs
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace WalletWasabi.BitcoinCore.Monitor
+{
+ public class RpcStatus : IEquatable<RpcStatus>
+ {
+ public string Status { get; }
+ public bool Success { get; }
+ public ulong Headers { get; }
+ public ulong Blocks { get; }
+ public int PeersCount { get; }
+ public bool Synchronized { get; }
+
+ public static RpcStatus Unresponsive = new RpcStatus(false, 0, 0, 0);
+
+ public static RpcStatus Responsive(ulong headers, ulong blocks, int peersCount) => new RpcStatus(true, headers, blocks, peersCount);
+
+ private RpcStatus(bool success, ulong headers, ulong blocks, int peersCount)
+ {
+ Synchronized = false;
+ if (success)
+ {
+ var diff = headers - blocks;
+ if (peersCount == 0)
+ {
+ Status = "Bitcoin Core is connecting...";
+ }
+ else if (diff == 0)
+ {
+ Synchronized = true;
+ Status = "Bitcoin Core is synchronized";
+ }
+ else
+ {
+ Status = $"Bitcoin Core is downloading {diff} blocks...";
+ }
+ }
+ else
+ {
+ Status = "Bitcoin Core is unresponsive";
+ }
+
+ Success = success;
+ Headers = headers;
+ Blocks = blocks;
+ PeersCount = peersCount;
+ }
+
+ public override string ToString() => Status;
+
+ #region EqualityAndComparison
+
+ public override bool Equals(object obj) => obj is RpcStatus rpcStatus && this == rpcStatus;
+
+ public bool Equals(RpcStatus other) => this == other;
+
+ public override int GetHashCode() => Status.GetHashCode();
+
+ public static bool operator ==(RpcStatus x, RpcStatus y) => y?.Status == x?.Status;
+
+ public static bool operator !=(RpcStatus x, RpcStatus y) => !(x == y);
+
+ #endregion EqualityAndComparison
+ }
+}

Add status monitor