using System; using System.Collections.Concurrent; using System.Collections.Immutable; using System.Net.Http; using System.Threading.Tasks; using BTCPayServer.Monero.Configuration; using BTCPayServer.Monero.RPC; using BTCPayServer.Monero.RPC.Models; using NBitcoin; namespace BTCPayServer.Monero.Services { public class MoneroRPCProvider { private readonly MoneroLikeConfiguration _moneroLikeConfiguration; private readonly EventAggregator _eventAggregator; public ImmutableDictionary DaemonRpcClients; public ImmutableDictionary WalletRpcClients; private ConcurrentDictionary _summaries = new ConcurrentDictionary(); public ConcurrentDictionary Summaries => _summaries; public MoneroRPCProvider(MoneroLikeConfiguration moneroLikeConfiguration, EventAggregator eventAggregator, IHttpClientFactory httpClientFactory) { _moneroLikeConfiguration = moneroLikeConfiguration; _eventAggregator = eventAggregator; DaemonRpcClients = _moneroLikeConfiguration.MoneroLikeConfigurationItems.ToImmutableDictionary(pair => pair.Key, pair => new JsonRpcClient(pair.Value.DaemonRpcUri, "", "", httpClientFactory.CreateClient())); WalletRpcClients = _moneroLikeConfiguration.MoneroLikeConfigurationItems.ToImmutableDictionary(pair => pair.Key, pair => new JsonRpcClient(pair.Value.InternalWalletRpcUri, "", "", httpClientFactory.CreateClient())); } public bool IsAvailable(string cryptoCode) { cryptoCode = cryptoCode.ToUpperInvariant(); return _summaries.ContainsKey(cryptoCode) && IsAvailable(_summaries[cryptoCode]); } private bool IsAvailable(MoneroLikeSummary summary) { return summary.Synced && summary.WalletAvailable; } public async Task UpdateSummary(string cryptoCode) { if (!DaemonRpcClients.TryGetValue(cryptoCode.ToUpperInvariant(), out var daemonRpcClient) || !WalletRpcClients.TryGetValue(cryptoCode.ToUpperInvariant(), out var walletRpcClient)) { return null; } var summary = new MoneroLikeSummary(); try { var daemonResult = await daemonRpcClient.SendCommandAsync("sync_info", JsonRpcClient.NoRequestModel.Instance); summary.TargetHeight = daemonResult.TargetHeight ?? daemonResult.Height; summary.Synced = !daemonResult.TargetHeight.HasValue || (daemonResult.Height >= daemonResult.TargetHeight && daemonResult.TargetHeight > 0); summary.CurrentHeight = daemonResult.Height; summary.UpdatedAt = DateTime.Now; summary.DaemonAvailable = true; } catch { summary.DaemonAvailable = false; } try { var walletResult = await walletRpcClient.SendCommandAsync( "get_height", JsonRpcClient.NoRequestModel.Instance); summary.WalletHeight = walletResult.Height; summary.WalletAvailable = true; } catch { summary.WalletAvailable = false; } var changed = !_summaries.ContainsKey(cryptoCode) || IsAvailable(cryptoCode) != IsAvailable(summary); _summaries.AddOrReplace(cryptoCode, summary); if (changed) { _eventAggregator.Publish(new MoneroDaemonStateChange() {Summary = summary, CryptoCode = cryptoCode}); } return summary; } public class MoneroDaemonStateChange { public string CryptoCode { get; set; } public MoneroLikeSummary Summary { get; set; } } public class MoneroLikeSummary { public bool Synced { get; set; } public long CurrentHeight { get; set; } public long WalletHeight { get; set; } public long TargetHeight { get; set; } public DateTime UpdatedAt { get; set; } public bool DaemonAvailable { get; set; } public bool WalletAvailable { get; set; } } } }