2020-06-28 21:44:35 -05:00
|
|
|
using System;
|
2020-06-28 17:55:27 +09:00
|
|
|
using System.Collections.Concurrent;
|
2017-12-17 01:04:20 +09:00
|
|
|
using System.Collections.Generic;
|
|
|
|
using System.Linq;
|
|
|
|
using System.Threading;
|
|
|
|
using System.Threading.Tasks;
|
2020-06-28 17:55:27 +09:00
|
|
|
using BTCPayServer.Events;
|
2017-12-17 01:04:20 +09:00
|
|
|
using BTCPayServer.Logging;
|
|
|
|
using Microsoft.Extensions.Hosting;
|
2020-06-28 17:55:27 +09:00
|
|
|
using Microsoft.Extensions.Logging;
|
2017-12-17 01:04:20 +09:00
|
|
|
using NBXplorer;
|
|
|
|
using NBXplorer.Models;
|
|
|
|
|
2018-01-08 02:36:41 +09:00
|
|
|
namespace BTCPayServer.HostedServices
|
2017-12-17 01:04:20 +09:00
|
|
|
{
|
|
|
|
public enum NBXplorerState
|
|
|
|
{
|
|
|
|
NotConnected,
|
|
|
|
Synching,
|
|
|
|
Ready
|
|
|
|
}
|
2017-12-17 14:17:42 +09:00
|
|
|
|
2018-01-08 04:14:35 +09:00
|
|
|
public class NBXplorerDashboard
|
|
|
|
{
|
|
|
|
public class NBXplorerSummary
|
|
|
|
{
|
2019-05-29 09:43:50 +00:00
|
|
|
public BTCPayNetworkBase Network { get; set; }
|
2018-01-08 04:14:35 +09:00
|
|
|
public NBXplorerState State { get; set; }
|
|
|
|
public StatusResult Status { get; set; }
|
2018-01-13 01:05:38 +09:00
|
|
|
public string Error { get; set; }
|
2018-01-08 04:14:35 +09:00
|
|
|
}
|
2020-06-28 22:07:48 -05:00
|
|
|
|
|
|
|
readonly ConcurrentDictionary<string, NBXplorerSummary> _Summaries = new ConcurrentDictionary<string, NBXplorerSummary>();
|
2019-05-29 09:43:50 +00:00
|
|
|
public void Publish(BTCPayNetworkBase network, NBXplorerState state, StatusResult status, string error)
|
2018-01-08 04:14:35 +09:00
|
|
|
{
|
2018-01-13 01:05:38 +09:00
|
|
|
var summary = new NBXplorerSummary() { Network = network, State = state, Status = status, Error = error };
|
2019-12-24 08:20:44 +01:00
|
|
|
_Summaries.AddOrUpdate(network.CryptoCode.ToUpperInvariant(), summary, (k, v) => summary);
|
2018-01-08 04:14:35 +09:00
|
|
|
}
|
|
|
|
|
|
|
|
public bool IsFullySynched()
|
|
|
|
{
|
2020-03-10 17:42:53 +09:00
|
|
|
return _Summaries.All(s => s.Value.Status?.IsFullySynched is true);
|
2018-01-08 04:14:35 +09:00
|
|
|
}
|
|
|
|
|
2018-03-02 14:03:18 -05:00
|
|
|
public bool IsFullySynched(string cryptoCode, out NBXplorerSummary summary)
|
2018-01-19 17:39:15 +09:00
|
|
|
{
|
2020-06-28 17:55:27 +09:00
|
|
|
return _Summaries.TryGetValue(cryptoCode.ToUpperInvariant(), out summary) &&
|
2020-03-10 17:42:53 +09:00
|
|
|
summary.Status?.IsFullySynched is true;
|
2018-01-19 17:39:15 +09:00
|
|
|
}
|
2018-10-26 23:07:39 +09:00
|
|
|
public NBXplorerSummary Get(string cryptoCode)
|
|
|
|
{
|
2019-05-07 13:58:55 +09:00
|
|
|
_Summaries.TryGetValue(cryptoCode.ToUpperInvariant(), out var summary);
|
2018-10-26 23:07:39 +09:00
|
|
|
return summary;
|
|
|
|
}
|
2018-01-08 04:14:35 +09:00
|
|
|
public IEnumerable<NBXplorerSummary> GetAll()
|
|
|
|
{
|
|
|
|
return _Summaries.Values;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-01-08 02:36:41 +09:00
|
|
|
public class NBXplorerWaiters : IHostedService
|
|
|
|
{
|
2020-06-28 22:07:48 -05:00
|
|
|
readonly List<NBXplorerWaiter> _Waiters = new List<NBXplorerWaiter>();
|
2021-11-22 17:16:08 +09:00
|
|
|
public NBXplorerWaiters(NBXplorerDashboard dashboard, ExplorerClientProvider explorerClientProvider, EventAggregator eventAggregator, Logs logs)
|
2018-01-08 02:36:41 +09:00
|
|
|
{
|
2018-01-08 04:14:35 +09:00
|
|
|
foreach (var explorer in explorerClientProvider.GetAll())
|
2018-01-08 02:36:41 +09:00
|
|
|
{
|
2021-11-22 17:16:08 +09:00
|
|
|
_Waiters.Add(new NBXplorerWaiter(dashboard, explorer.Item1, explorer.Item2, eventAggregator, logs));
|
2018-01-08 02:36:41 +09:00
|
|
|
}
|
|
|
|
}
|
|
|
|
public Task StartAsync(CancellationToken cancellationToken)
|
|
|
|
{
|
|
|
|
return Task.WhenAll(_Waiters.Select(w => w.StartAsync(cancellationToken)).ToArray());
|
|
|
|
}
|
|
|
|
|
|
|
|
public Task StopAsync(CancellationToken cancellationToken)
|
|
|
|
{
|
|
|
|
return Task.WhenAll(_Waiters.Select(w => w.StopAsync(cancellationToken)).ToArray());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-12-17 01:04:20 +09:00
|
|
|
public class NBXplorerWaiter : IHostedService
|
|
|
|
{
|
2018-01-08 02:36:41 +09:00
|
|
|
|
2021-11-22 17:16:08 +09:00
|
|
|
public NBXplorerWaiter(NBXplorerDashboard dashboard, BTCPayNetwork network, ExplorerClient client, EventAggregator aggregator, Logs logs)
|
2017-12-17 01:04:20 +09:00
|
|
|
{
|
2021-11-22 17:16:08 +09:00
|
|
|
this.Logs = logs;
|
2018-01-08 02:36:41 +09:00
|
|
|
_Network = network;
|
2017-12-17 01:04:20 +09:00
|
|
|
_Client = client;
|
2017-12-17 14:17:42 +09:00
|
|
|
_Aggregator = aggregator;
|
2018-01-08 04:14:35 +09:00
|
|
|
_Dashboard = dashboard;
|
2020-03-10 17:42:53 +09:00
|
|
|
_Dashboard.Publish(_Network, State, null, null);
|
2017-12-17 01:04:20 +09:00
|
|
|
}
|
|
|
|
|
2020-06-28 22:07:48 -05:00
|
|
|
readonly NBXplorerDashboard _Dashboard;
|
2021-11-22 17:16:08 +09:00
|
|
|
|
|
|
|
public Logs Logs { get; }
|
|
|
|
|
2020-06-28 22:07:48 -05:00
|
|
|
readonly BTCPayNetwork _Network;
|
|
|
|
readonly EventAggregator _Aggregator;
|
|
|
|
readonly ExplorerClient _Client;
|
2018-01-10 02:07:42 +09:00
|
|
|
|
|
|
|
CancellationTokenSource _Cts;
|
|
|
|
Task _Loop;
|
2017-12-17 01:04:20 +09:00
|
|
|
public Task StartAsync(CancellationToken cancellationToken)
|
|
|
|
{
|
2018-01-10 02:07:42 +09:00
|
|
|
_Cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
|
|
|
|
_Loop = StartLoop(_Cts.Token);
|
2017-12-17 01:04:20 +09:00
|
|
|
return Task.CompletedTask;
|
|
|
|
}
|
|
|
|
|
2018-01-10 02:07:42 +09:00
|
|
|
private async Task StartLoop(CancellationToken cancellation)
|
2017-12-17 01:04:20 +09:00
|
|
|
{
|
2018-01-10 02:07:42 +09:00
|
|
|
Logs.PayServer.LogInformation($"Starting listening NBXplorer ({_Network.CryptoCode})");
|
2017-12-17 01:04:20 +09:00
|
|
|
try
|
|
|
|
{
|
2018-01-10 02:07:42 +09:00
|
|
|
while (!cancellation.IsCancellationRequested)
|
|
|
|
{
|
|
|
|
try
|
|
|
|
{
|
|
|
|
while (await StepAsync(cancellation))
|
|
|
|
{
|
2017-12-17 01:04:20 +09:00
|
|
|
|
2018-01-10 02:07:42 +09:00
|
|
|
}
|
|
|
|
await Task.Delay(PollInterval, cancellation);
|
|
|
|
}
|
|
|
|
catch (Exception ex) when (!cancellation.IsCancellationRequested)
|
|
|
|
{
|
|
|
|
Logs.PayServer.LogError(ex, $"Unhandled exception in NBXplorerWaiter ({_Network.CryptoCode})");
|
|
|
|
await Task.Delay(TimeSpan.FromSeconds(10), cancellation);
|
|
|
|
}
|
|
|
|
}
|
2017-12-17 01:04:20 +09:00
|
|
|
}
|
2018-01-10 02:07:42 +09:00
|
|
|
catch when (cancellation.IsCancellationRequested) { }
|
2017-12-17 01:04:20 +09:00
|
|
|
}
|
|
|
|
|
2018-01-10 02:07:42 +09:00
|
|
|
private async Task<bool> StepAsync(CancellationToken cancellation)
|
2017-12-17 01:04:20 +09:00
|
|
|
{
|
|
|
|
var oldState = State;
|
2018-01-13 01:05:38 +09:00
|
|
|
string error = null;
|
2017-12-17 01:04:20 +09:00
|
|
|
StatusResult status = null;
|
2018-01-10 02:07:42 +09:00
|
|
|
try
|
2017-12-17 01:04:20 +09:00
|
|
|
{
|
2018-01-10 02:07:42 +09:00
|
|
|
switch (State)
|
|
|
|
{
|
|
|
|
case NBXplorerState.NotConnected:
|
|
|
|
status = await _Client.GetStatusAsync(cancellation);
|
|
|
|
if (status != null)
|
|
|
|
{
|
|
|
|
if (status.IsFullySynched)
|
|
|
|
{
|
|
|
|
State = NBXplorerState.Ready;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
State = NBXplorerState.Synching;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case NBXplorerState.Synching:
|
|
|
|
status = await _Client.GetStatusAsync(cancellation);
|
|
|
|
if (status == null)
|
|
|
|
{
|
|
|
|
State = NBXplorerState.NotConnected;
|
|
|
|
}
|
|
|
|
else if (status.IsFullySynched)
|
2017-12-17 01:04:20 +09:00
|
|
|
{
|
|
|
|
State = NBXplorerState.Ready;
|
|
|
|
}
|
2018-01-10 02:07:42 +09:00
|
|
|
break;
|
|
|
|
case NBXplorerState.Ready:
|
|
|
|
status = await _Client.GetStatusAsync(cancellation);
|
|
|
|
if (status == null)
|
|
|
|
{
|
|
|
|
State = NBXplorerState.NotConnected;
|
|
|
|
}
|
|
|
|
else if (!status.IsFullySynched)
|
2017-12-17 01:04:20 +09:00
|
|
|
{
|
|
|
|
State = NBXplorerState.Synching;
|
|
|
|
}
|
2018-01-10 02:07:42 +09:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2017-12-17 01:04:20 +09:00
|
|
|
}
|
2018-01-13 01:05:38 +09:00
|
|
|
catch (Exception ex) when (!cancellation.IsCancellationRequested)
|
2018-01-10 02:07:42 +09:00
|
|
|
{
|
2018-01-13 01:05:38 +09:00
|
|
|
error = ex.Message;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2020-06-28 17:55:27 +09:00
|
|
|
if (status == null && error == null)
|
2018-01-13 01:05:38 +09:00
|
|
|
error = $"{_Network.CryptoCode}: NBXplorer does not support this cryptocurrency";
|
2017-12-17 11:07:11 +09:00
|
|
|
|
2020-06-28 17:55:27 +09:00
|
|
|
if (status != null && error == null)
|
2018-01-13 01:05:38 +09:00
|
|
|
{
|
2021-01-27 14:39:38 +09:00
|
|
|
if (status.NetworkType != _Network.NBitcoinNetwork.ChainName)
|
|
|
|
error = $"{_Network.CryptoCode}: NBXplorer is on a different ChainType (actual: {status.NetworkType}, expected: {_Network.NBitcoinNetwork.ChainName})";
|
2018-01-10 02:07:42 +09:00
|
|
|
}
|
2018-01-13 01:05:38 +09:00
|
|
|
|
|
|
|
if (error != null)
|
2018-01-10 02:07:42 +09:00
|
|
|
{
|
|
|
|
State = NBXplorerState.NotConnected;
|
2018-01-13 01:05:38 +09:00
|
|
|
status = null;
|
2018-05-10 11:56:46 +09:00
|
|
|
Logs.PayServer.LogError($"{_Network.CryptoCode}: NBXplorer error `{error}`");
|
2018-01-10 02:07:42 +09:00
|
|
|
}
|
2018-01-13 01:05:38 +09:00
|
|
|
|
2018-02-17 01:34:40 +09:00
|
|
|
_Dashboard.Publish(_Network, State, status, error);
|
2017-12-17 01:04:20 +09:00
|
|
|
if (oldState != State)
|
|
|
|
{
|
2017-12-17 11:07:11 +09:00
|
|
|
if (State == NBXplorerState.Synching)
|
|
|
|
{
|
2018-01-10 02:07:42 +09:00
|
|
|
PollInterval = TimeSpan.FromSeconds(10);
|
2017-12-17 11:07:11 +09:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2018-01-10 02:07:42 +09:00
|
|
|
PollInterval = TimeSpan.FromMinutes(1);
|
2017-12-17 11:07:11 +09:00
|
|
|
}
|
2018-01-08 02:36:41 +09:00
|
|
|
_Aggregator.Publish(new NBXplorerStateChangedEvent(_Network, oldState, State));
|
2017-12-17 01:04:20 +09:00
|
|
|
}
|
|
|
|
return oldState != State;
|
|
|
|
}
|
|
|
|
|
2018-01-10 02:07:42 +09:00
|
|
|
public TimeSpan PollInterval { get; set; } = TimeSpan.FromMinutes(1.0);
|
2017-12-17 01:04:20 +09:00
|
|
|
|
|
|
|
public NBXplorerState State { get; private set; }
|
|
|
|
|
|
|
|
public Task StopAsync(CancellationToken cancellationToken)
|
|
|
|
{
|
2018-01-10 02:07:42 +09:00
|
|
|
_Cts.Cancel();
|
|
|
|
return _Loop;
|
2017-12-17 01:04:20 +09:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|