mirror of
https://github.com/aljazceru/btcpayserver.git
synced 2026-02-22 06:34:36 +01:00
Improve checkout page load time by fetching recommended fee in the background periodically (#5672)
This commit is contained in:
@@ -39,7 +39,7 @@ namespace BTCPayServer.Tests
|
||||
if (!Directory.Exists(_Directory))
|
||||
Directory.CreateDirectory(_Directory);
|
||||
|
||||
NetworkProvider = networkProvider;
|
||||
_NetworkProvider = networkProvider;
|
||||
ExplorerNode = new RPCClient(RPCCredentialString.Parse(GetEnvironment("TESTS_BTCRPCCONNECTION", "server=http://127.0.0.1:43782;ceiwHEbqWI83:DwubwWsoo3")), NetworkProvider.GetNetwork<BTCPayNetwork>("BTC").NBitcoinNetwork);
|
||||
ExplorerNode.ScanRPCCapabilities();
|
||||
|
||||
@@ -214,8 +214,14 @@ namespace BTCPayServer.Tests
|
||||
{
|
||||
return new TestAccount(this);
|
||||
}
|
||||
|
||||
public BTCPayNetworkProvider NetworkProvider { get; private set; }
|
||||
BTCPayNetworkProvider _NetworkProvider;
|
||||
public BTCPayNetworkProvider NetworkProvider
|
||||
{
|
||||
get
|
||||
{
|
||||
return PayTester?.Networks ?? _NetworkProvider;
|
||||
}
|
||||
}
|
||||
public RPCClient ExplorerNode
|
||||
{
|
||||
get; set;
|
||||
|
||||
@@ -91,7 +91,13 @@ namespace BTCPayServer.Tests
|
||||
"test" + isTestnet,
|
||||
prov.GetService<IHttpClientFactory>(),
|
||||
isTestnet);
|
||||
mempoolSpaceFeeProvider.CachedOnly = true;
|
||||
await Assert.ThrowsAsync<InvalidOperationException>(() => mempoolSpaceFeeProvider.GetFeeRateAsync());
|
||||
mempoolSpaceFeeProvider.CachedOnly = false;
|
||||
var rates = await mempoolSpaceFeeProvider.GetFeeRatesAsync();
|
||||
mempoolSpaceFeeProvider.CachedOnly = true;
|
||||
await mempoolSpaceFeeProvider.GetFeeRateAsync();
|
||||
mempoolSpaceFeeProvider.CachedOnly = false;
|
||||
Assert.NotEmpty(rates);
|
||||
|
||||
|
||||
@@ -121,10 +127,7 @@ namespace BTCPayServer.Tests
|
||||
//ENSURE THESE ARE LOGICAL
|
||||
Assert.True(recommendedFees[0].FeeRate >= recommendedFees[1].FeeRate, $"{recommendedFees[0].Target}:{recommendedFees[0].FeeRate} >= {recommendedFees[1].Target}:{recommendedFees[1].FeeRate}");
|
||||
Assert.True(recommendedFees[1].FeeRate >= recommendedFees[2].FeeRate, $"{recommendedFees[1].Target}:{recommendedFees[1].FeeRate} >= {recommendedFees[2].Target}:{recommendedFees[2].FeeRate}");
|
||||
Assert.True(recommendedFees[2].FeeRate >= recommendedFees[3].FeeRate, $"{recommendedFees[2].Target}:{recommendedFees[2].FeeRate} >= {recommendedFees[3].Target}:{recommendedFees[3].FeeRate}");
|
||||
|
||||
|
||||
|
||||
Assert.True(recommendedFees[2].FeeRate >= recommendedFees[3].FeeRate, $"{recommendedFees[2].Target}:{recommendedFees[2].FeeRate} >= {recommendedFees[3].Target}:{recommendedFees[3].FeeRate}");
|
||||
}
|
||||
}
|
||||
[Fact]
|
||||
|
||||
@@ -368,7 +368,8 @@ namespace BTCPayServer.Hosting
|
||||
services.TryAddSingleton<WalletReceiveService>();
|
||||
services.AddSingleton<IHostedService>(provider => provider.GetService<WalletReceiveService>());
|
||||
services.TryAddSingleton<CurrencyNameTable>(CurrencyNameTable.Instance);
|
||||
services.TryAddSingleton<IFeeProviderFactory, FeeProviderFactory>();
|
||||
services.AddScheduledTask<FeeProviderFactory>(TimeSpan.FromMinutes(3.0));
|
||||
services.AddSingleton<IFeeProviderFactory, FeeProviderFactory>(f => f.GetRequiredService<FeeProviderFactory>());
|
||||
|
||||
services.Configure<MvcOptions>((o) =>
|
||||
{
|
||||
|
||||
@@ -1,14 +1,18 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.HostedServices;
|
||||
using Microsoft.AspNetCore.HttpOverrides;
|
||||
using Microsoft.Extensions.Caching.Memory;
|
||||
using NBitcoin;
|
||||
|
||||
namespace BTCPayServer.Services.Fees;
|
||||
|
||||
public class FeeProviderFactory : IFeeProviderFactory
|
||||
public class FeeProviderFactory : IFeeProviderFactory, IPeriodicTask
|
||||
{
|
||||
public FeeProviderFactory(
|
||||
BTCPayServerEnvironment Environment,
|
||||
@@ -16,7 +20,7 @@ public class FeeProviderFactory : IFeeProviderFactory
|
||||
IHttpClientFactory HttpClientFactory,
|
||||
IMemoryCache MemoryCache)
|
||||
{
|
||||
_FeeProviders = new ();
|
||||
_FeeProviders = new();
|
||||
|
||||
// TODO: Pluginify this
|
||||
foreach ((var network, var client) in ExplorerClients.GetAll())
|
||||
@@ -29,7 +33,10 @@ public class FeeProviderFactory : IFeeProviderFactory
|
||||
$"MempoolSpaceFeeProvider-{network.CryptoCode}",
|
||||
HttpClientFactory,
|
||||
network is BTCPayNetwork n &&
|
||||
n.NBitcoinNetwork.ChainName == ChainName.Testnet));
|
||||
n.NBitcoinNetwork.ChainName == ChainName.Testnet)
|
||||
{
|
||||
CachedOnly = true
|
||||
});
|
||||
}
|
||||
providers.Add(new NBXplorerFeeProvider(client));
|
||||
providers.Add(new StaticFeeProvider(new FeeRate(100L, 1)));
|
||||
@@ -42,4 +49,27 @@ public class FeeProviderFactory : IFeeProviderFactory
|
||||
{
|
||||
return _FeeProviders.TryGetValue(network, out var prov) ? prov : throw new NotSupportedException($"No fee provider for this network ({network.CryptoCode})");
|
||||
}
|
||||
|
||||
public async Task Do(CancellationToken cancellationToken)
|
||||
{
|
||||
try
|
||||
{
|
||||
await RefreshCache(_FeeProviders.Values);
|
||||
}
|
||||
// Do not spam logs if mempoolspace is down
|
||||
catch (TaskCanceledException)
|
||||
{
|
||||
}
|
||||
catch (HttpRequestException)
|
||||
{
|
||||
}
|
||||
}
|
||||
private Task RefreshCache(IEnumerable<IFeeProvider> feeProviders) => Task.WhenAll(feeProviders.Select(fp => RefreshCache(fp)));
|
||||
private Task RefreshCache(IFeeProvider fp) =>
|
||||
fp switch
|
||||
{
|
||||
FallbackFeeProvider ffp => Task.WhenAll(ffp.Providers.Select(p => RefreshCache(p))),
|
||||
MempoolSpaceFeeProvider mempool => mempool.RefreshCache(),
|
||||
_ => Task.CompletedTask
|
||||
};
|
||||
}
|
||||
|
||||
@@ -52,14 +52,23 @@ public class MempoolSpaceFeeProvider(
|
||||
var a = (decimal)(target - lb.Blocks) / (decimal)(hb.Blocks - lb.Blocks);
|
||||
return new FeeRate((1 - a) * lb.FeeRate.SatoshiPerByte + a * hb.FeeRate.SatoshiPerByte);
|
||||
}
|
||||
readonly TimeSpan Expiration = TimeSpan.FromMinutes(25);
|
||||
public async Task RefreshCache()
|
||||
{
|
||||
var rate = await GetFeeRatesCore();
|
||||
memoryCache.Set(cacheKey, rate, Expiration);
|
||||
}
|
||||
|
||||
public bool CachedOnly { get; set; }
|
||||
internal async Task<BlockFeeRate[]> GetFeeRatesAsync()
|
||||
{
|
||||
if (CachedOnly)
|
||||
return memoryCache.Get(cacheKey) as BlockFeeRate[] ?? throw new InvalidOperationException("Fee rates unavailable");
|
||||
try
|
||||
{
|
||||
return (await memoryCache.GetOrCreateAsync(cacheKey, async entry =>
|
||||
{
|
||||
entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(5);
|
||||
entry.AbsoluteExpirationRelativeToNow = Expiration;
|
||||
return await GetFeeRatesCore();
|
||||
}))!;
|
||||
}
|
||||
@@ -73,8 +82,7 @@ public class MempoolSpaceFeeProvider(
|
||||
async Task<BlockFeeRate[]> GetFeeRatesCore()
|
||||
{
|
||||
var client = httpClientFactory.CreateClient(nameof(MempoolSpaceFeeProvider));
|
||||
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(10));
|
||||
using var result = await client.GetAsync(ExplorerLink, cts.Token);
|
||||
using var result = await client.GetAsync(ExplorerLink);
|
||||
result.EnsureSuccessStatusCode();
|
||||
var recommendedFees = await result.Content.ReadAsAsync<Dictionary<string, decimal>>();
|
||||
var r = new List<BlockFeeRate>();
|
||||
@@ -97,8 +105,8 @@ public class MempoolSpaceFeeProvider(
|
||||
{
|
||||
// Randomize a bit
|
||||
ordered[i] = ordered[i] with { FeeRate = new FeeRate(RandomizeByPercentage(ordered[i].FeeRate.SatoshiPerByte, 10m)) };
|
||||
if (i > 0) // Make sure feerate always increase
|
||||
ordered[i] = ordered[i] with { FeeRate = FeeRate.Max(ordered[i - 1].FeeRate, ordered[i].FeeRate) };
|
||||
if (i > 0) // Make sure feerate always decrease
|
||||
ordered[i] = ordered[i] with { FeeRate = FeeRate.Min(ordered[i - 1].FeeRate, ordered[i].FeeRate) };
|
||||
}
|
||||
return ordered;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user