mirror of
https://github.com/aljazceru/btcpayserver.git
synced 2025-12-18 14:34:23 +01:00
Refactoring: Do not query database when asking for Coinaverage rates, periodically get exchange list
This commit is contained in:
@@ -23,7 +23,6 @@ namespace BTCPayServer.Controllers
|
|||||||
{
|
{
|
||||||
private UserManager<ApplicationUser> _UserManager;
|
private UserManager<ApplicationUser> _UserManager;
|
||||||
SettingsRepository _SettingsRepository;
|
SettingsRepository _SettingsRepository;
|
||||||
private IRateProviderFactory _RateProviderFactory;
|
|
||||||
|
|
||||||
public ServerController(UserManager<ApplicationUser> userManager,
|
public ServerController(UserManager<ApplicationUser> userManager,
|
||||||
IRateProviderFactory rateProviderFactory,
|
IRateProviderFactory rateProviderFactory,
|
||||||
@@ -31,7 +30,6 @@ namespace BTCPayServer.Controllers
|
|||||||
{
|
{
|
||||||
_UserManager = userManager;
|
_UserManager = userManager;
|
||||||
_SettingsRepository = settingsRepository;
|
_SettingsRepository = settingsRepository;
|
||||||
_RateProviderFactory = rateProviderFactory;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Route("server/rates")]
|
[Route("server/rates")]
|
||||||
@@ -47,22 +45,6 @@ namespace BTCPayServer.Controllers
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class TestCoinAverageAuthenticator : ICoinAverageAuthenticator
|
|
||||||
{
|
|
||||||
private RatesSetting settings;
|
|
||||||
|
|
||||||
public TestCoinAverageAuthenticator(RatesSetting settings)
|
|
||||||
{
|
|
||||||
this.settings = settings;
|
|
||||||
}
|
|
||||||
public Task AddHeader(HttpRequestMessage message)
|
|
||||||
{
|
|
||||||
var sig = settings.GetCoinAverageSignature();
|
|
||||||
if (sig != null)
|
|
||||||
message.Headers.Add("X-signature", settings.GetCoinAverageSignature());
|
|
||||||
return Task.CompletedTask;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
[Route("server/rates")]
|
[Route("server/rates")]
|
||||||
[HttpPost]
|
[HttpPost]
|
||||||
public async Task<IActionResult> Rates(RatesViewModel vm)
|
public async Task<IActionResult> Rates(RatesViewModel vm)
|
||||||
@@ -73,10 +55,14 @@ namespace BTCPayServer.Controllers
|
|||||||
rates.CacheInMinutes = vm.CacheMinutes;
|
rates.CacheInMinutes = vm.CacheMinutes;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (rates.GetCoinAverageSignature() != null)
|
var settings = new CoinAverageSettings()
|
||||||
|
{
|
||||||
|
KeyPair = (vm.PublicKey, vm.PrivateKey)
|
||||||
|
};
|
||||||
|
if (settings.GetCoinAverageSignature() != null)
|
||||||
{
|
{
|
||||||
await new CoinAverageRateProvider("BTC")
|
await new CoinAverageRateProvider("BTC")
|
||||||
{ Authenticator = new TestCoinAverageAuthenticator(rates) }.TestAuthAsync();
|
{ Authenticator = settings }.TestAuthAsync();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
@@ -86,7 +72,6 @@ namespace BTCPayServer.Controllers
|
|||||||
if (!ModelState.IsValid)
|
if (!ModelState.IsValid)
|
||||||
return View(vm);
|
return View(vm);
|
||||||
await _SettingsRepository.UpdateSetting(rates);
|
await _SettingsRepository.UpdateSetting(rates);
|
||||||
((BTCPayRateProviderFactory)_RateProviderFactory).CacheSpan = TimeSpan.FromMinutes(vm.CacheMinutes);
|
|
||||||
StatusMessage = "Rate settings successfully updated";
|
StatusMessage = "Rate settings successfully updated";
|
||||||
return RedirectToAction(nameof(Rates));
|
return RedirectToAction(nameof(Rates));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ using BTCPayServer.Services;
|
|||||||
using BTCPayServer.Services.Rates;
|
using BTCPayServer.Services.Rates;
|
||||||
using Microsoft.Extensions.Hosting;
|
using Microsoft.Extensions.Hosting;
|
||||||
using BTCPayServer.Logging;
|
using BTCPayServer.Logging;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
namespace BTCPayServer.HostedServices
|
namespace BTCPayServer.HostedServices
|
||||||
{
|
{
|
||||||
@@ -15,41 +16,78 @@ namespace BTCPayServer.HostedServices
|
|||||||
{
|
{
|
||||||
private SettingsRepository _SettingsRepository;
|
private SettingsRepository _SettingsRepository;
|
||||||
private IRateProviderFactory _RateProviderFactory;
|
private IRateProviderFactory _RateProviderFactory;
|
||||||
|
private CoinAverageSettings _coinAverageSettings;
|
||||||
public RatesHostedService(SettingsRepository repo,
|
public RatesHostedService(SettingsRepository repo,
|
||||||
|
CoinAverageSettings coinAverageSettings,
|
||||||
IRateProviderFactory rateProviderFactory)
|
IRateProviderFactory rateProviderFactory)
|
||||||
{
|
{
|
||||||
this._SettingsRepository = repo;
|
this._SettingsRepository = repo;
|
||||||
_RateProviderFactory = rateProviderFactory;
|
_RateProviderFactory = rateProviderFactory;
|
||||||
|
_coinAverageSettings = coinAverageSettings;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
CancellationTokenSource _Cts = new CancellationTokenSource();
|
||||||
|
|
||||||
|
List<Task> _Tasks = new List<Task>();
|
||||||
|
|
||||||
public Task StartAsync(CancellationToken cancellationToken)
|
public Task StartAsync(CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
Init();
|
_Tasks.Add(RefreshCoinAverageSupportedExchanges(_Cts.Token));
|
||||||
|
_Tasks.Add(RefreshCoinAverageSettings(_Cts.Token));
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
async void Init()
|
|
||||||
{
|
|
||||||
var rates = (await _SettingsRepository.GetSettingAsync<RatesSetting>()) ?? new RatesSetting();
|
|
||||||
_RateProviderFactory.CacheSpan = TimeSpan.FromMinutes(rates.CacheInMinutes);
|
|
||||||
|
|
||||||
//string[] availableExchanges = null;
|
async Task Timer(Func<Task> act, CancellationToken cancellation, [CallerMemberName]string caller = null)
|
||||||
//// So we don't run this in testing
|
{
|
||||||
//if(_RateProviderFactory is BTCPayRateProviderFactory)
|
while (!cancellation.IsCancellationRequested)
|
||||||
//{
|
{
|
||||||
// try
|
try
|
||||||
// {
|
{
|
||||||
// await new CoinAverageRateProvider("BTC").GetExchangeTickersAsync();
|
await act();
|
||||||
// }
|
}
|
||||||
// catch(Exception ex)
|
catch (OperationCanceledException) when (cancellation.IsCancellationRequested)
|
||||||
// {
|
{
|
||||||
// Logs.PayServer.LogWarning(ex, "Failed to get exchange tickers");
|
}
|
||||||
// }
|
catch (Exception ex)
|
||||||
//}
|
{
|
||||||
|
Logs.PayServer.LogWarning(ex, caller + " failed");
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await Task.Delay(TimeSpan.FromMinutes(1), cancellation);
|
||||||
|
}
|
||||||
|
catch (OperationCanceledException) when (cancellation.IsCancellationRequested) { }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Task RefreshCoinAverageSupportedExchanges(CancellationToken cancellation)
|
||||||
|
{
|
||||||
|
return Timer(async () =>
|
||||||
|
{
|
||||||
|
var tickers = await new CoinAverageRateProvider("BTC").GetExchangeTickersAsync();
|
||||||
|
await Task.Delay(TimeSpan.FromHours(5), cancellation);
|
||||||
|
}, cancellation);
|
||||||
|
}
|
||||||
|
|
||||||
|
Task RefreshCoinAverageSettings(CancellationToken cancellation)
|
||||||
|
{
|
||||||
|
return Timer(async () =>
|
||||||
|
{
|
||||||
|
var rates = (await _SettingsRepository.GetSettingAsync<RatesSetting>()) ?? new RatesSetting();
|
||||||
|
_RateProviderFactory.CacheSpan = TimeSpan.FromMinutes(rates.CacheInMinutes);
|
||||||
|
if (!string.IsNullOrWhiteSpace(rates.PrivateKey) && !string.IsNullOrWhiteSpace(rates.PublicKey))
|
||||||
|
{
|
||||||
|
_coinAverageSettings.KeyPair = (rates.PublicKey, rates.PrivateKey);
|
||||||
|
}
|
||||||
|
await _SettingsRepository.WaitSettingsChanged<RatesSetting>(cancellation);
|
||||||
|
}, cancellation);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task StopAsync(CancellationToken cancellationToken)
|
public Task StopAsync(CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
return Task.CompletedTask;
|
_Cts.Cancel();
|
||||||
|
return Task.WhenAll(_Tasks.ToArray());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -109,7 +109,8 @@ namespace BTCPayServer.Hosting
|
|||||||
services.AddSingleton<BTCPayServerEnvironment>();
|
services.AddSingleton<BTCPayServerEnvironment>();
|
||||||
services.TryAddSingleton<TokenRepository>();
|
services.TryAddSingleton<TokenRepository>();
|
||||||
services.TryAddSingleton<EventAggregator>();
|
services.TryAddSingleton<EventAggregator>();
|
||||||
services.TryAddSingleton<ICoinAverageAuthenticator, BTCPayCoinAverageAuthenticator>();
|
services.TryAddSingleton<CoinAverageSettings>();
|
||||||
|
services.TryAddSingleton<ICoinAverageAuthenticator, CoinAverageSettings>();
|
||||||
services.TryAddSingleton<ApplicationDbContextFactory>(o =>
|
services.TryAddSingleton<ApplicationDbContextFactory>(o =>
|
||||||
{
|
{
|
||||||
var opts = o.GetRequiredService<BTCPayServerOptions>();
|
var opts = o.GetRequiredService<BTCPayServerOptions>();
|
||||||
|
|||||||
@@ -59,41 +59,11 @@ namespace BTCPayServer.Services.Rates
|
|||||||
|
|
||||||
public class RatesSetting
|
public class RatesSetting
|
||||||
{
|
{
|
||||||
private static readonly DateTime _epochUtc = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
|
|
||||||
public string PublicKey { get; set; }
|
public string PublicKey { get; set; }
|
||||||
public string PrivateKey { get; set; }
|
public string PrivateKey { get; set; }
|
||||||
[DefaultValue(15)]
|
[DefaultValue(15)]
|
||||||
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Populate)]
|
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Populate)]
|
||||||
public int CacheInMinutes { get; set; } = 15;
|
public int CacheInMinutes { get; set; } = 15;
|
||||||
|
|
||||||
public string GetCoinAverageSignature()
|
|
||||||
{
|
|
||||||
if (string.IsNullOrEmpty(PublicKey) || string.IsNullOrEmpty(PrivateKey))
|
|
||||||
return null;
|
|
||||||
var timestamp = (int)((DateTime.UtcNow - _epochUtc).TotalSeconds);
|
|
||||||
var payload = timestamp + "." + PublicKey;
|
|
||||||
var digestValueBytes = new HMACSHA256(Encoding.ASCII.GetBytes(PrivateKey)).ComputeHash(Encoding.ASCII.GetBytes(payload));
|
|
||||||
var digestValueHex = NBitcoin.DataEncoders.Encoders.Hex.EncodeData(digestValueBytes);
|
|
||||||
return payload + "." + digestValueHex;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
public class BTCPayCoinAverageAuthenticator : ICoinAverageAuthenticator
|
|
||||||
{
|
|
||||||
private SettingsRepository settingsRepo;
|
|
||||||
|
|
||||||
public BTCPayCoinAverageAuthenticator(SettingsRepository settingsRepo)
|
|
||||||
{
|
|
||||||
this.settingsRepo = settingsRepo;
|
|
||||||
}
|
|
||||||
public async Task AddHeader(HttpRequestMessage message)
|
|
||||||
{
|
|
||||||
var settings = (await settingsRepo.GetSettingAsync<RatesSetting>()) ?? new RatesSetting();
|
|
||||||
var signature = settings.GetCoinAverageSignature();
|
|
||||||
if (signature != null)
|
|
||||||
{
|
|
||||||
message.Headers.Add("X-signature", signature);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public interface ICoinAverageAuthenticator
|
public interface ICoinAverageAuthenticator
|
||||||
|
|||||||
42
BTCPayServer/Services/Rates/CoinAverageSettings.cs
Normal file
42
BTCPayServer/Services/Rates/CoinAverageSettings.cs
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Net.Http;
|
||||||
|
using System.Security.Cryptography;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace BTCPayServer.Services.Rates
|
||||||
|
{
|
||||||
|
public class CoinAverageSettings : ICoinAverageAuthenticator
|
||||||
|
{
|
||||||
|
private static readonly DateTime _epochUtc = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
|
||||||
|
|
||||||
|
public (String PublicKey, String PrivateKey)? KeyPair { get; set; }
|
||||||
|
public string[] AvailableExchanges { get; set; } = Array.Empty<string>();
|
||||||
|
|
||||||
|
public Task AddHeader(HttpRequestMessage message)
|
||||||
|
{
|
||||||
|
var signature = GetCoinAverageSignature();
|
||||||
|
if (signature != null)
|
||||||
|
{
|
||||||
|
message.Headers.Add("X-signature", signature);
|
||||||
|
}
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string GetCoinAverageSignature()
|
||||||
|
{
|
||||||
|
var keyPair = KeyPair;
|
||||||
|
if (!keyPair.HasValue)
|
||||||
|
return null;
|
||||||
|
if (string.IsNullOrEmpty(keyPair.Value.PublicKey) || string.IsNullOrEmpty(keyPair.Value.PrivateKey))
|
||||||
|
return null;
|
||||||
|
var timestamp = (int)((DateTime.UtcNow - _epochUtc).TotalSeconds);
|
||||||
|
var payload = timestamp + "." + keyPair.Value.PublicKey;
|
||||||
|
var digestValueBytes = new HMACSHA256(Encoding.ASCII.GetBytes(keyPair.Value.PrivateKey)).ComputeHash(Encoding.ASCII.GetBytes(payload));
|
||||||
|
var digestValueHex = NBitcoin.DataEncoders.Encoders.Hex.EncodeData(digestValueBytes);
|
||||||
|
return payload + "." + digestValueHex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,6 +8,7 @@ using Microsoft.EntityFrameworkCore;
|
|||||||
using BTCPayServer.Models;
|
using BTCPayServer.Models;
|
||||||
using Microsoft.EntityFrameworkCore.Infrastructure.Internal;
|
using Microsoft.EntityFrameworkCore.Infrastructure.Internal;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
|
using System.Threading;
|
||||||
|
|
||||||
namespace BTCPayServer.Services
|
namespace BTCPayServer.Services
|
||||||
{
|
{
|
||||||
@@ -51,6 +52,22 @@ namespace BTCPayServer.Services
|
|||||||
await ctx.SaveChangesAsync();
|
await ctx.SaveChangesAsync();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
IReadOnlyCollection<TaskCompletionSource<bool>> value;
|
||||||
|
lock (_Subscriptions)
|
||||||
|
{
|
||||||
|
if(_Subscriptions.TryGetValue(typeof(T), out value))
|
||||||
|
{
|
||||||
|
_Subscriptions.Remove(typeof(T));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(value != null)
|
||||||
|
{
|
||||||
|
foreach(var v in value)
|
||||||
|
{
|
||||||
|
v.TrySetResult(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private T Deserialize<T>(string value)
|
private T Deserialize<T>(string value)
|
||||||
@@ -62,5 +79,35 @@ namespace BTCPayServer.Services
|
|||||||
{
|
{
|
||||||
return JsonConvert.SerializeObject(obj);
|
return JsonConvert.SerializeObject(obj);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
MultiValueDictionary<Type, TaskCompletionSource<bool>> _Subscriptions = new MultiValueDictionary<Type, TaskCompletionSource<bool>>();
|
||||||
|
public async Task WaitSettingsChanged<T>(CancellationToken cancellation)
|
||||||
|
{
|
||||||
|
var tcs = new TaskCompletionSource<bool>();
|
||||||
|
using (cancellation.Register(() =>
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
tcs.TrySetCanceled();
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
|
}))
|
||||||
|
{
|
||||||
|
lock (_Subscriptions)
|
||||||
|
{
|
||||||
|
_Subscriptions.Add(typeof(T), tcs);
|
||||||
|
}
|
||||||
|
#pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
|
||||||
|
tcs.Task.ContinueWith(_ =>
|
||||||
|
{
|
||||||
|
lock (_Subscriptions)
|
||||||
|
{
|
||||||
|
_Subscriptions.Remove(typeof(T), tcs);
|
||||||
|
}
|
||||||
|
}, TaskScheduler.Default);
|
||||||
|
#pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
|
||||||
|
await tcs.Task;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,7 +29,7 @@
|
|||||||
<label class="sr-only" asp-for="PrivateKey"></label>
|
<label class="sr-only" asp-for="PrivateKey"></label>
|
||||||
<input asp-for="PrivateKey" style="width:50%;" class="form-control" placeholder="Private key" />
|
<input asp-for="PrivateKey" style="width:50%;" class="form-control" placeholder="Private key" />
|
||||||
<span asp-validation-for="PrivateKey" class="text-danger"></span>
|
<span asp-validation-for="PrivateKey" class="text-danger"></span>
|
||||||
<p class="form-text text-muted">You can find the information on <a href="https://bitcoinaverage.com/en/apikeys">bitcoinaverage api key page</a></p>
|
<p class="form-text text-muted">You can find the information on <a target="_blank" href="https://bitcoinaverage.com/en/apikeys">bitcoinaverage api key page</a></p>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label asp-for="CacheMinutes"></label>
|
<label asp-for="CacheMinutes"></label>
|
||||||
|
|||||||
Reference in New Issue
Block a user