2017-12-17 01:04:20 +09:00
using System ;
using Microsoft.Extensions.Logging ;
using System.Collections.Generic ;
using System.Linq ;
using System.Threading ;
using System.Threading.Tasks ;
using BTCPayServer.Logging ;
using Microsoft.Extensions.Hosting ;
using NBXplorer ;
using NBXplorer.Models ;
using System.Collections.Concurrent ;
2017-12-17 14:17:42 +09:00
using BTCPayServer.Events ;
2017-12-17 01:04:20 +09:00
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
{
public BTCPayNetwork Network { get ; set ; }
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
}
ConcurrentDictionary < string , NBXplorerSummary > _Summaries = new ConcurrentDictionary < string , NBXplorerSummary > ( ) ;
2018-01-13 01:05:38 +09:00
public void Publish ( BTCPayNetwork 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 } ;
2018-01-08 04:14:35 +09:00
_Summaries . AddOrUpdate ( network . CryptoCode , summary , ( k , v ) = > summary ) ;
}
public bool IsFullySynched ( )
{
return _Summaries . All ( s = > s . Value . Status ! = null & & s . Value . Status . IsFullySynched ) ;
}
2018-03-02 14:03:18 -05:00
public bool IsFullySynched ( string cryptoCode , out NBXplorerSummary summary )
2018-01-19 17:39:15 +09:00
{
2018-03-02 14:03:18 -05:00
return _Summaries . TryGetValue ( cryptoCode , out summary ) & &
summary . Status ! = null & &
summary . Status . IsFullySynched ;
2018-01-19 17:39:15 +09:00
}
2018-10-26 23:07:39 +09:00
public NBXplorerSummary Get ( string cryptoCode )
{
_Summaries . TryGetValue ( cryptoCode , out var summary ) ;
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
{
List < NBXplorerWaiter > _Waiters = new List < NBXplorerWaiter > ( ) ;
2018-01-08 04:14:35 +09:00
public NBXplorerWaiters ( NBXplorerDashboard dashboard , ExplorerClientProvider explorerClientProvider , EventAggregator eventAggregator )
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
{
2018-01-08 04:14:35 +09:00
_Waiters . Add ( new NBXplorerWaiter ( dashboard , explorer . Item1 , explorer . Item2 , eventAggregator ) ) ;
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
2018-01-08 04:14:35 +09:00
public NBXplorerWaiter ( NBXplorerDashboard dashboard , BTCPayNetwork network , ExplorerClient client , EventAggregator aggregator )
2017-12-17 01:04:20 +09:00
{
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 ;
2017-12-17 01:04:20 +09:00
}
2018-01-08 04:14:35 +09:00
NBXplorerDashboard _Dashboard ;
2018-01-08 02:36:41 +09:00
BTCPayNetwork _Network ;
2017-12-17 14:17:42 +09:00
EventAggregator _Aggregator ;
2017-12-17 01:04:20 +09:00
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 ;
}
if ( status = = null & & error = = null )
error = $"{_Network.CryptoCode}: NBXplorer does not support this cryptocurrency" ;
2017-12-17 11:07:11 +09:00
2018-01-13 01:05:38 +09:00
if ( status ! = null & & error = = null )
{
2018-04-19 16:54:25 +09:00
if ( status . NetworkType ! = _Network . NBitcoinNetwork . NetworkType )
error = $"{_Network.CryptoCode}: NBXplorer is on a different ChainType (actual: {status.NetworkType}, expected: {_Network.NBitcoinNetwork.NetworkType})" ;
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
}
}
}