mirror of
https://github.com/aljazceru/btcpayserver.git
synced 2025-12-18 06:24:24 +01:00
Poll and cache rates in parallel
This commit is contained in:
@@ -120,36 +120,67 @@ namespace BTCPayServer.Tests
|
|||||||
.Build();
|
.Build();
|
||||||
_Host.Start();
|
_Host.Start();
|
||||||
InvoiceRepository = (InvoiceRepository)_Host.Services.GetService(typeof(InvoiceRepository));
|
InvoiceRepository = (InvoiceRepository)_Host.Services.GetService(typeof(InvoiceRepository));
|
||||||
StoreRepository = (StoreRepository)_Host.Services.GetService(typeof(StoreRepository));
|
StoreRepository = (StoreRepository)_Host.Services.GetService(typeof(StoreRepository));
|
||||||
var rateProvider = (RateProviderFactory)_Host.Services.GetService(typeof(RateProviderFactory));
|
|
||||||
rateProvider.DirectProviders.Clear();
|
|
||||||
|
|
||||||
var coinAverageMock = new MockRateProvider();
|
if (MockRates)
|
||||||
coinAverageMock.ExchangeRates.Add(new Rating.ExchangeRate()
|
|
||||||
{
|
{
|
||||||
Exchange = "coinaverage",
|
var rateProvider = (RateProviderFactory)_Host.Services.GetService(typeof(RateProviderFactory));
|
||||||
CurrencyPair = CurrencyPair.Parse("BTC_USD"),
|
rateProvider.Providers.Clear();
|
||||||
BidAsk = new BidAsk(5000m)
|
|
||||||
});
|
var coinAverageMock = new MockRateProvider();
|
||||||
coinAverageMock.ExchangeRates.Add(new Rating.ExchangeRate()
|
coinAverageMock.ExchangeRates.Add(new Rating.ExchangeRate()
|
||||||
{
|
{
|
||||||
Exchange = "coinaverage",
|
Exchange = "coinaverage",
|
||||||
CurrencyPair = CurrencyPair.Parse("BTC_CAD"),
|
CurrencyPair = CurrencyPair.Parse("BTC_USD"),
|
||||||
BidAsk = new BidAsk(4500m)
|
BidAsk = new BidAsk(5000m)
|
||||||
});
|
});
|
||||||
coinAverageMock.ExchangeRates.Add(new Rating.ExchangeRate()
|
coinAverageMock.ExchangeRates.Add(new Rating.ExchangeRate()
|
||||||
{
|
{
|
||||||
Exchange = "coinaverage",
|
Exchange = "coinaverage",
|
||||||
CurrencyPair = CurrencyPair.Parse("LTC_BTC"),
|
CurrencyPair = CurrencyPair.Parse("BTC_CAD"),
|
||||||
BidAsk = new BidAsk(0.001m)
|
BidAsk = new BidAsk(4500m)
|
||||||
});
|
});
|
||||||
coinAverageMock.ExchangeRates.Add(new Rating.ExchangeRate()
|
coinAverageMock.ExchangeRates.Add(new Rating.ExchangeRate()
|
||||||
{
|
{
|
||||||
Exchange = "coinaverage",
|
Exchange = "coinaverage",
|
||||||
CurrencyPair = CurrencyPair.Parse("LTC_USD"),
|
CurrencyPair = CurrencyPair.Parse("LTC_BTC"),
|
||||||
BidAsk = new BidAsk(500m)
|
BidAsk = new BidAsk(0.001m)
|
||||||
});
|
});
|
||||||
rateProvider.DirectProviders.Add("coinaverage", coinAverageMock);
|
coinAverageMock.ExchangeRates.Add(new Rating.ExchangeRate()
|
||||||
|
{
|
||||||
|
Exchange = "coinaverage",
|
||||||
|
CurrencyPair = CurrencyPair.Parse("LTC_USD"),
|
||||||
|
BidAsk = new BidAsk(500m)
|
||||||
|
});
|
||||||
|
rateProvider.Providers.Add("coinaverage", coinAverageMock);
|
||||||
|
|
||||||
|
var bitflyerMock = new MockRateProvider();
|
||||||
|
bitflyerMock.ExchangeRates.Add(new Rating.ExchangeRate()
|
||||||
|
{
|
||||||
|
Exchange = "bitflyer",
|
||||||
|
CurrencyPair = CurrencyPair.Parse("BTC_JPY"),
|
||||||
|
BidAsk = new BidAsk(700000m)
|
||||||
|
});
|
||||||
|
rateProvider.Providers.Add("bitflyer", bitflyerMock);
|
||||||
|
|
||||||
|
var quadrigacx = new MockRateProvider();
|
||||||
|
quadrigacx.ExchangeRates.Add(new Rating.ExchangeRate()
|
||||||
|
{
|
||||||
|
Exchange = "quadrigacx",
|
||||||
|
CurrencyPair = CurrencyPair.Parse("BTC_CAD"),
|
||||||
|
BidAsk = new BidAsk(6000m)
|
||||||
|
});
|
||||||
|
rateProvider.Providers.Add("quadrigacx", quadrigacx);
|
||||||
|
|
||||||
|
var bittrex = new MockRateProvider();
|
||||||
|
bittrex.ExchangeRates.Add(new Rating.ExchangeRate()
|
||||||
|
{
|
||||||
|
Exchange = "bittrex",
|
||||||
|
CurrencyPair = CurrencyPair.Parse("DOGE_BTC"),
|
||||||
|
BidAsk = new BidAsk(0.004m)
|
||||||
|
});
|
||||||
|
rateProvider.Providers.Add("bittrex", bittrex);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public string HostName
|
public string HostName
|
||||||
@@ -177,7 +208,7 @@ namespace BTCPayServer.Tests
|
|||||||
{
|
{
|
||||||
context.User = new ClaimsPrincipal(new ClaimsIdentity(new[] { new Claim(ClaimTypes.NameIdentifier, userId) }, Policies.CookieAuthentication));
|
context.User = new ClaimsPrincipal(new ClaimsIdentity(new[] { new Claim(ClaimTypes.NameIdentifier, userId) }, Policies.CookieAuthentication));
|
||||||
}
|
}
|
||||||
if(storeId != null)
|
if (storeId != null)
|
||||||
{
|
{
|
||||||
context.SetStoreData(GetService<StoreRepository>().FindStore(storeId, userId).GetAwaiter().GetResult());
|
context.SetStoreData(GetService<StoreRepository>().FindStore(storeId, userId).GetAwaiter().GetResult());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ using Xunit;
|
|||||||
using NBXplorer.DerivationStrategy;
|
using NBXplorer.DerivationStrategy;
|
||||||
using BTCPayServer.Payments;
|
using BTCPayServer.Payments;
|
||||||
using BTCPayServer.Payments.Lightning;
|
using BTCPayServer.Payments.Lightning;
|
||||||
|
using BTCPayServer.Tests.Logging;
|
||||||
|
|
||||||
namespace BTCPayServer.Tests
|
namespace BTCPayServer.Tests
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -398,6 +398,7 @@ namespace BTCPayServer.Tests
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
[Trait("Flaky", "Flaky")]
|
||||||
public void CanSetLightningServer()
|
public void CanSetLightningServer()
|
||||||
{
|
{
|
||||||
using (var tester = ServerTester.Create())
|
using (var tester = ServerTester.Create())
|
||||||
@@ -546,18 +547,21 @@ namespace BTCPayServer.Tests
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
[Trait("Flaky", "Flaky")]
|
||||||
public void CanSendLightningPaymentCLightning()
|
public void CanSendLightningPaymentCLightning()
|
||||||
{
|
{
|
||||||
ProcessLightningPayment(LightningConnectionType.CLightning);
|
ProcessLightningPayment(LightningConnectionType.CLightning);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
[Trait("Flaky", "Flaky")]
|
||||||
public void CanSendLightningPaymentCharge()
|
public void CanSendLightningPaymentCharge()
|
||||||
{
|
{
|
||||||
ProcessLightningPayment(LightningConnectionType.Charge);
|
ProcessLightningPayment(LightningConnectionType.Charge);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
[Trait("Flaky", "Flaky")]
|
||||||
public void CanSendLightningPaymentLnd()
|
public void CanSendLightningPaymentLnd()
|
||||||
{
|
{
|
||||||
ProcessLightningPayment(LightningConnectionType.LndREST);
|
ProcessLightningPayment(LightningConnectionType.LndREST);
|
||||||
@@ -943,8 +947,8 @@ namespace BTCPayServer.Tests
|
|||||||
user.RegisterDerivationScheme("BTC");
|
user.RegisterDerivationScheme("BTC");
|
||||||
List<decimal> rates = new List<decimal>();
|
List<decimal> rates = new List<decimal>();
|
||||||
rates.Add(CreateInvoice(tester, user, "coinaverage"));
|
rates.Add(CreateInvoice(tester, user, "coinaverage"));
|
||||||
var bitflyer = CreateInvoice(tester, user, "bitflyer");
|
var bitflyer = CreateInvoice(tester, user, "bitflyer", "JPY");
|
||||||
var bitflyer2 = CreateInvoice(tester, user, "bitflyer");
|
var bitflyer2 = CreateInvoice(tester, user, "bitflyer", "JPY");
|
||||||
Assert.Equal(bitflyer, bitflyer2); // Should be equal because cache
|
Assert.Equal(bitflyer, bitflyer2); // Should be equal because cache
|
||||||
rates.Add(bitflyer);
|
rates.Add(bitflyer);
|
||||||
|
|
||||||
@@ -955,7 +959,7 @@ namespace BTCPayServer.Tests
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static decimal CreateInvoice(ServerTester tester, TestAccount user, string exchange)
|
private static decimal CreateInvoice(ServerTester tester, TestAccount user, string exchange, string currency = "USD")
|
||||||
{
|
{
|
||||||
var storeController = user.GetController<StoresController>();
|
var storeController = user.GetController<StoresController>();
|
||||||
var vm = (RatesViewModel)((ViewResult)storeController.Rates()).Model;
|
var vm = (RatesViewModel)((ViewResult)storeController.Rates()).Model;
|
||||||
@@ -964,7 +968,7 @@ namespace BTCPayServer.Tests
|
|||||||
var invoice2 = user.BitPay.CreateInvoice(new Invoice()
|
var invoice2 = user.BitPay.CreateInvoice(new Invoice()
|
||||||
{
|
{
|
||||||
Price = 5000.0m,
|
Price = 5000.0m,
|
||||||
Currency = "USD",
|
Currency = currency,
|
||||||
PosData = "posData",
|
PosData = "posData",
|
||||||
OrderId = "orderId",
|
OrderId = "orderId",
|
||||||
ItemDesc = "Some description",
|
ItemDesc = "Some description",
|
||||||
@@ -978,12 +982,10 @@ namespace BTCPayServer.Tests
|
|||||||
{
|
{
|
||||||
using (var tester = ServerTester.Create())
|
using (var tester = ServerTester.Create())
|
||||||
{
|
{
|
||||||
tester.PayTester.MockRates = false;
|
|
||||||
tester.Start();
|
tester.Start();
|
||||||
var user = tester.NewAccount();
|
var user = tester.NewAccount();
|
||||||
user.GrantAccess();
|
user.GrantAccess();
|
||||||
user.RegisterDerivationScheme("BTC");
|
user.RegisterDerivationScheme("BTC");
|
||||||
|
|
||||||
// First we try payment with a merchant having only BTC
|
// First we try payment with a merchant having only BTC
|
||||||
var invoice1 = user.BitPay.CreateInvoice(new Invoice()
|
var invoice1 = user.BitPay.CreateInvoice(new Invoice()
|
||||||
{
|
{
|
||||||
@@ -994,7 +996,7 @@ namespace BTCPayServer.Tests
|
|||||||
ItemDesc = "Some description",
|
ItemDesc = "Some description",
|
||||||
FullNotifications = true
|
FullNotifications = true
|
||||||
}, Facade.Merchant);
|
}, Facade.Merchant);
|
||||||
|
Assert.Equal(Money.Coins(1.0m), invoice1.BtcPrice);
|
||||||
|
|
||||||
var storeController = user.GetController<StoresController>();
|
var storeController = user.GetController<StoresController>();
|
||||||
var vm = (RatesViewModel)((ViewResult)storeController.Rates()).Model;
|
var vm = (RatesViewModel)((ViewResult)storeController.Rates()).Model;
|
||||||
@@ -1013,11 +1015,9 @@ namespace BTCPayServer.Tests
|
|||||||
FullNotifications = true
|
FullNotifications = true
|
||||||
}, Facade.Merchant);
|
}, Facade.Merchant);
|
||||||
|
|
||||||
// The rate was 5000 USD per BTC
|
var expectedRate = 5000.0m * 0.6m;
|
||||||
// Now it should be 3000 USD per BTC
|
var expectedCoins = invoice2.Price / expectedRate;
|
||||||
// So the expected price should be
|
Assert.True(invoice2.BtcPrice.Almost(Money.Coins(expectedCoins), 0.00001m));
|
||||||
var expected = Money.Coins(5000m / 3000m);
|
|
||||||
Assert.True(invoice2.BtcPrice.Almost(expected, 0.00001m));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1132,7 +1132,7 @@ namespace BTCPayServer.Tests
|
|||||||
rateVm.ScriptTest = "BTC_USD,BTC_CAD,DOGE_USD,DOGE_CAD";
|
rateVm.ScriptTest = "BTC_USD,BTC_CAD,DOGE_USD,DOGE_CAD";
|
||||||
rateVm.Script = "DOGE_X = bittrex(DOGE_BTC) * BTC_X;\n" +
|
rateVm.Script = "DOGE_X = bittrex(DOGE_BTC) * BTC_X;\n" +
|
||||||
"X_CAD = quadrigacx(X_CAD);\n" +
|
"X_CAD = quadrigacx(X_CAD);\n" +
|
||||||
"X_X = gdax(X_X);";
|
"X_X = coinaverage(X_X);";
|
||||||
rateVm.Spread = 50;
|
rateVm.Spread = 50;
|
||||||
rateVm = Assert.IsType<RatesViewModel>(Assert.IsType<ViewResult>(store.Rates(rateVm, "Test").Result).Model);
|
rateVm = Assert.IsType<RatesViewModel>(Assert.IsType<ViewResult>(store.Rates(rateVm, "Test").Result).Model);
|
||||||
Assert.True(rateVm.TestRateRules.All(t => !t.Error));
|
Assert.True(rateVm.TestRateRules.All(t => !t.Error));
|
||||||
@@ -1669,11 +1669,14 @@ namespace BTCPayServer.Tests
|
|||||||
var factory = CreateBTCPayRateFactory();
|
var factory = CreateBTCPayRateFactory();
|
||||||
|
|
||||||
foreach (var result in factory
|
foreach (var result in factory
|
||||||
.DirectProviders
|
.Providers
|
||||||
.Select(p => (ExpectedName: p.Key, ResultAsync: p.Value.GetRatesAsync()))
|
.Where(p => p.Value is BackgroundFetcherRateProvider)
|
||||||
|
.Select(p => (ExpectedName: p.Key, ResultAsync: p.Value.GetRatesAsync(), Fetcher: (BackgroundFetcherRateProvider)p.Value))
|
||||||
.ToList())
|
.ToList())
|
||||||
{
|
{
|
||||||
|
result.Fetcher.InvalidateCache();
|
||||||
var exchangeRates = result.ResultAsync.Result;
|
var exchangeRates = result.ResultAsync.Result;
|
||||||
|
result.Fetcher.InvalidateCache();
|
||||||
Assert.NotNull(exchangeRates);
|
Assert.NotNull(exchangeRates);
|
||||||
Assert.NotEmpty(exchangeRates);
|
Assert.NotEmpty(exchangeRates);
|
||||||
Assert.NotEmpty(exchangeRates.ByExchange[result.ExpectedName]);
|
Assert.NotEmpty(exchangeRates.ByExchange[result.ExpectedName]);
|
||||||
@@ -1687,7 +1690,7 @@ namespace BTCPayServer.Tests
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
// Kraken emit one request only after first GetRates
|
// Kraken emit one request only after first GetRates
|
||||||
factory.DirectProviders["kraken"].GetRatesAsync().GetAwaiter().GetResult();
|
factory.Providers["kraken"].GetRatesAsync().GetAwaiter().GetResult();
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
@@ -1712,44 +1715,87 @@ namespace BTCPayServer.Tests
|
|||||||
|
|
||||||
private static RateProviderFactory CreateBTCPayRateFactory()
|
private static RateProviderFactory CreateBTCPayRateFactory()
|
||||||
{
|
{
|
||||||
return new RateProviderFactory(new MemoryCacheOptions() { ExpirationScanFrequency = TimeSpan.FromSeconds(1.0) }, null, new CoinAverageSettings());
|
return new RateProviderFactory(CreateMemoryCache(), null, new CoinAverageSettings());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static MemoryCacheOptions CreateMemoryCache()
|
||||||
|
{
|
||||||
|
return new MemoryCacheOptions() { ExpirationScanFrequency = TimeSpan.FromSeconds(1.0) };
|
||||||
|
}
|
||||||
|
|
||||||
|
class SpyRateProvider : IRateProvider
|
||||||
|
{
|
||||||
|
public bool Hit { get; set; }
|
||||||
|
public Task<ExchangeRates> GetRatesAsync()
|
||||||
|
{
|
||||||
|
Hit = true;
|
||||||
|
var rates = new ExchangeRates();
|
||||||
|
rates.Add(new ExchangeRate("coinaverage", CurrencyPair.Parse("BTC_USD"), new BidAsk(5000)));
|
||||||
|
return Task.FromResult(rates);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AssertHit()
|
||||||
|
{
|
||||||
|
Assert.True(Hit, "Should have hit the provider");
|
||||||
|
Hit = false;
|
||||||
|
}
|
||||||
|
public void AssertNotHit()
|
||||||
|
{
|
||||||
|
Assert.False(Hit, "Should have not hit the provider");
|
||||||
|
Hit = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void CheckRatesProvider()
|
public void CheckRatesProvider()
|
||||||
{
|
{
|
||||||
var coinAverage = new CoinAverageRateProvider();
|
var spy = new SpyRateProvider();
|
||||||
var rates = coinAverage.GetRatesAsync().GetAwaiter().GetResult();
|
|
||||||
Assert.NotNull(rates.GetRate("coinaverage", new CurrencyPair("BTC", "JPY")));
|
|
||||||
var ratesBitpay = new BitpayRateProvider(new Bitpay(new Key(), new Uri("https://bitpay.com/"))).GetRatesAsync().GetAwaiter().GetResult();
|
|
||||||
Assert.NotNull(ratesBitpay.GetRate("bitpay", new CurrencyPair("BTC", "JPY")));
|
|
||||||
|
|
||||||
RateRules.TryParse("X_X = coinaverage(X_X);", out var rateRules);
|
RateRules.TryParse("X_X = coinaverage(X_X);", out var rateRules);
|
||||||
|
|
||||||
var factory = CreateBTCPayRateFactory();
|
var factory = CreateBTCPayRateFactory();
|
||||||
var fetcher = new RateFetcher(CreateBTCPayRateFactory());
|
factory.Providers.Clear();
|
||||||
factory.CacheSpan = TimeSpan.FromSeconds(10);
|
factory.Providers.Add("coinaverage", new CachedRateProvider("coinaverage", spy, new MemoryCache(CreateMemoryCache())));
|
||||||
|
factory.Providers.Add("bittrex", new CachedRateProvider("bittrex", spy, new MemoryCache(CreateMemoryCache())));
|
||||||
|
factory.CacheSpan = TimeSpan.FromSeconds(1);
|
||||||
|
|
||||||
|
var fetcher = new RateFetcher(factory);
|
||||||
|
|
||||||
var fetchedRate = fetcher.FetchRate(CurrencyPair.Parse("BTC_USD"), rateRules).GetAwaiter().GetResult();
|
var fetchedRate = fetcher.FetchRate(CurrencyPair.Parse("BTC_USD"), rateRules).GetAwaiter().GetResult();
|
||||||
Assert.False(fetchedRate.Cached);
|
spy.AssertHit();
|
||||||
fetchedRate = fetcher.FetchRate(CurrencyPair.Parse("BTC_USD"), rateRules).GetAwaiter().GetResult();
|
fetchedRate = fetcher.FetchRate(CurrencyPair.Parse("BTC_USD"), rateRules).GetAwaiter().GetResult();
|
||||||
Assert.True(fetchedRate.Cached);
|
spy.AssertNotHit();
|
||||||
|
|
||||||
Thread.Sleep(11000);
|
Thread.Sleep(3000);
|
||||||
fetchedRate = fetcher.FetchRate(CurrencyPair.Parse("BTC_USD"), rateRules).GetAwaiter().GetResult();
|
fetchedRate = fetcher.FetchRate(CurrencyPair.Parse("BTC_USD"), rateRules).GetAwaiter().GetResult();
|
||||||
Assert.False(fetchedRate.Cached);
|
spy.AssertHit();
|
||||||
fetchedRate = fetcher.FetchRate(CurrencyPair.Parse("BTC_USD"), rateRules).GetAwaiter().GetResult();
|
fetchedRate = fetcher.FetchRate(CurrencyPair.Parse("BTC_USD"), rateRules).GetAwaiter().GetResult();
|
||||||
Assert.True(fetchedRate.Cached);
|
spy.AssertNotHit();
|
||||||
// Should cache at exchange level so this should hit the cache
|
// Should cache at exchange level so this should hit the cache
|
||||||
var fetchedRate2 = fetcher.FetchRate(CurrencyPair.Parse("LTC_USD"), rateRules).GetAwaiter().GetResult();
|
var fetchedRate2 = fetcher.FetchRate(CurrencyPair.Parse("LTC_USD"), rateRules).GetAwaiter().GetResult();
|
||||||
Assert.True(fetchedRate.Cached);
|
spy.AssertNotHit();
|
||||||
Assert.NotEqual(fetchedRate.BidAsk.Bid, fetchedRate2.BidAsk.Bid);
|
Assert.Null(fetchedRate2.BidAsk);
|
||||||
|
Assert.Equal(RateRulesErrors.RateUnavailable, fetchedRate2.Errors.First());
|
||||||
|
|
||||||
// Should cache at exchange level this should not hit the cache as it is different exchange
|
// Should cache at exchange level this should not hit the cache as it is different exchange
|
||||||
RateRules.TryParse("X_X = bittrex(X_X);", out rateRules);
|
RateRules.TryParse("X_X = bittrex(X_X);", out rateRules);
|
||||||
fetchedRate = fetcher.FetchRate(CurrencyPair.Parse("BTC_USD"), rateRules).GetAwaiter().GetResult();
|
fetchedRate = fetcher.FetchRate(CurrencyPair.Parse("BTC_USD"), rateRules).GetAwaiter().GetResult();
|
||||||
Assert.False(fetchedRate.Cached);
|
spy.AssertHit();
|
||||||
|
|
||||||
|
factory.Providers.Clear();
|
||||||
|
var fetch = new BackgroundFetcherRateProvider(spy);
|
||||||
|
factory.Providers.Add("bittrex", fetch);
|
||||||
|
fetchedRate = fetcher.FetchRate(CurrencyPair.Parse("BTC_USD"), rateRules).GetAwaiter().GetResult();
|
||||||
|
spy.AssertHit();
|
||||||
|
fetchedRate = fetcher.FetchRate(CurrencyPair.Parse("BTC_USD"), rateRules).GetAwaiter().GetResult();
|
||||||
|
spy.AssertNotHit();
|
||||||
|
fetch.UpdateIfNecessary().GetAwaiter().GetResult();
|
||||||
|
spy.AssertNotHit();
|
||||||
|
fetch.RefreshRate = TimeSpan.FromSeconds(1.0);
|
||||||
|
Thread.Sleep(1020);
|
||||||
|
fetchedRate = fetcher.FetchRate(CurrencyPair.Parse("BTC_USD"), rateRules).GetAwaiter().GetResult();
|
||||||
|
spy.AssertNotHit();
|
||||||
|
fetch.UpdateIfNecessary().GetAwaiter().GetResult();
|
||||||
|
spy.AssertHit();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool IsMapped(Invoice invoice, ApplicationDbContext ctx)
|
private static bool IsMapped(Invoice invoice, ApplicationDbContext ctx)
|
||||||
|
|||||||
@@ -195,12 +195,9 @@ namespace BTCPayServer.Controllers
|
|||||||
var allRateRuleErrors = string.Join(", ", rateResult.Errors.ToArray());
|
var allRateRuleErrors = string.Join(", ", rateResult.Errors.ToArray());
|
||||||
logs.Write($"{pair.Key}: Rate rule error ({allRateRuleErrors})");
|
logs.Write($"{pair.Key}: Rate rule error ({allRateRuleErrors})");
|
||||||
}
|
}
|
||||||
if (rateResult.ExchangeExceptions.Count != 0)
|
foreach (var ex in rateResult.ExchangeExceptions)
|
||||||
{
|
{
|
||||||
foreach (var ex in rateResult.ExchangeExceptions)
|
logs.Write($"{pair.Key}: Exception reaching exchange {ex.ExchangeName} ({ex.Exception.Message})");
|
||||||
{
|
|
||||||
logs.Write($"{pair.Key}: Exception reaching exchange {ex.ExchangeName} ({ex.Exception.Message})");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}).ToArray());
|
}).ToArray());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,16 +33,41 @@ namespace BTCPayServer.HostedServices
|
|||||||
return new[]
|
return new[]
|
||||||
{
|
{
|
||||||
CreateLoopTask(RefreshCoinAverageSupportedExchanges),
|
CreateLoopTask(RefreshCoinAverageSupportedExchanges),
|
||||||
CreateLoopTask(RefreshCoinAverageSettings)
|
CreateLoopTask(RefreshCoinAverageSettings),
|
||||||
|
CreateLoopTask(RefreshRates)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
async Task RefreshRates()
|
||||||
|
{
|
||||||
|
|
||||||
|
using (var timeout = CancellationTokenSource.CreateLinkedTokenSource(Cancellation))
|
||||||
|
{
|
||||||
|
timeout.CancelAfter(TimeSpan.FromSeconds(20.0));
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await Task.WhenAll(_RateProviderFactory.Providers
|
||||||
|
.Select(p => (Fetcher: p.Value as BackgroundFetcherRateProvider, ExchangeName: p.Key)).Where(p => p.Fetcher != null)
|
||||||
|
.Select(p => p.Fetcher.UpdateIfNecessary().ContinueWith(t =>
|
||||||
|
{
|
||||||
|
if (t.Result.Exception != null)
|
||||||
|
{
|
||||||
|
Logs.PayServer.LogWarning($"Error while contacting {p.ExchangeName}: {t.Result.Exception.Message}");
|
||||||
|
}
|
||||||
|
}, TaskScheduler.Default))
|
||||||
|
.ToArray()).WithCancellation(timeout.Token);
|
||||||
|
}
|
||||||
|
catch (OperationCanceledException) when (timeout.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
await Task.Delay(TimeSpan.FromSeconds(30), Cancellation);
|
||||||
|
}
|
||||||
|
|
||||||
async Task RefreshCoinAverageSupportedExchanges()
|
async Task RefreshCoinAverageSupportedExchanges()
|
||||||
{
|
{
|
||||||
await new SynchronizationContextRemover();
|
|
||||||
var tickers = await new CoinAverageRateProvider() { Authenticator = _coinAverageSettings }.GetExchangeTickersAsync();
|
var tickers = await new CoinAverageRateProvider() { Authenticator = _coinAverageSettings }.GetExchangeTickersAsync();
|
||||||
var exchanges = new CoinAverageExchanges();
|
var exchanges = new CoinAverageExchanges();
|
||||||
foreach(var item in tickers
|
foreach (var item in tickers
|
||||||
.Exchanges
|
.Exchanges
|
||||||
.Select(c => new CoinAverageExchange(c.Name, c.DisplayName)))
|
.Select(c => new CoinAverageExchange(c.Name, c.DisplayName)))
|
||||||
{
|
{
|
||||||
@@ -54,7 +79,6 @@ namespace BTCPayServer.HostedServices
|
|||||||
|
|
||||||
async Task RefreshCoinAverageSettings()
|
async Task RefreshCoinAverageSettings()
|
||||||
{
|
{
|
||||||
await new SynchronizationContextRemover();
|
|
||||||
var rates = (await _SettingsRepository.GetSettingAsync<RatesSetting>()) ?? new RatesSetting();
|
var rates = (await _SettingsRepository.GetSettingAsync<RatesSetting>()) ?? new RatesSetting();
|
||||||
_RateProviderFactory.CacheSpan = TimeSpan.FromMinutes(rates.CacheInMinutes);
|
_RateProviderFactory.CacheSpan = TimeSpan.FromMinutes(rates.CacheInMinutes);
|
||||||
if (!string.IsNullOrWhiteSpace(rates.PrivateKey) && !string.IsNullOrWhiteSpace(rates.PublicKey))
|
if (!string.IsNullOrWhiteSpace(rates.PrivateKey) && !string.IsNullOrWhiteSpace(rates.PublicKey))
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ namespace BTCPayServer.Services.Rates
|
|||||||
{
|
{
|
||||||
public class BackgroundFetcherRateProvider : IRateProvider
|
public class BackgroundFetcherRateProvider : IRateProvider
|
||||||
{
|
{
|
||||||
class LatestFetch
|
public class LatestFetch
|
||||||
{
|
{
|
||||||
public ExchangeRates Latest;
|
public ExchangeRates Latest;
|
||||||
public DateTimeOffset Timestamp;
|
public DateTimeOffset Timestamp;
|
||||||
@@ -41,20 +41,24 @@ namespace BTCPayServer.Services.Rates
|
|||||||
get
|
get
|
||||||
{
|
{
|
||||||
var latest = _Latest;
|
var latest = _Latest;
|
||||||
if (latest == null)
|
if (latest == null || latest.Exception != null)
|
||||||
return DateTimeOffset.UtcNow;
|
return DateTimeOffset.UtcNow;
|
||||||
return latest.Timestamp + RefreshRate;
|
return latest.Timestamp + RefreshRate;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<bool> UpdateIfNecessary()
|
public async Task<LatestFetch> UpdateIfNecessary()
|
||||||
{
|
{
|
||||||
if (NextUpdate <= DateTimeOffset.UtcNow)
|
if (NextUpdate <= DateTimeOffset.UtcNow)
|
||||||
{
|
{
|
||||||
await Fetch();
|
try
|
||||||
return true;
|
{
|
||||||
|
await Fetch();
|
||||||
|
}
|
||||||
|
catch { } // Exception is inside _Latest
|
||||||
|
return _Latest;
|
||||||
}
|
}
|
||||||
return false;
|
return _Latest;
|
||||||
}
|
}
|
||||||
|
|
||||||
LatestFetch _Latest;
|
LatestFetch _Latest;
|
||||||
@@ -77,7 +81,14 @@ namespace BTCPayServer.Services.Rates
|
|||||||
}
|
}
|
||||||
fetch.Timestamp = DateTimeOffset.UtcNow;
|
fetch.Timestamp = DateTimeOffset.UtcNow;
|
||||||
_Latest = fetch;
|
_Latest = fetch;
|
||||||
|
if (fetch.Exception != null)
|
||||||
|
ExceptionDispatchInfo.Capture(fetch.Exception).Throw();
|
||||||
return fetch;
|
return fetch;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void InvalidateCache()
|
||||||
|
{
|
||||||
|
_Latest = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ namespace BTCPayServer.Services.Rates
|
|||||||
get;
|
get;
|
||||||
set;
|
set;
|
||||||
} = TimeSpan.FromMinutes(1.0);
|
} = TimeSpan.FromMinutes(1.0);
|
||||||
public IMemoryCache MemoryCache { get => _MemoryCache; private set => _MemoryCache = value; }
|
public IMemoryCache MemoryCache { get => _MemoryCache; set => _MemoryCache = value; }
|
||||||
|
|
||||||
public Task<ExchangeRates> GetRatesAsync()
|
public Task<ExchangeRates> GetRatesAsync()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ namespace BTCPayServer.Services.Rates
|
|||||||
public class FallbackRateProvider : IRateProvider
|
public class FallbackRateProvider : IRateProvider
|
||||||
{
|
{
|
||||||
IRateProvider[] _Providers;
|
IRateProvider[] _Providers;
|
||||||
public bool Used { get; set; }
|
|
||||||
public FallbackRateProvider(IRateProvider[] providers)
|
public FallbackRateProvider(IRateProvider[] providers)
|
||||||
{
|
{
|
||||||
if (providers == null)
|
if (providers == null)
|
||||||
@@ -19,7 +18,6 @@ namespace BTCPayServer.Services.Rates
|
|||||||
|
|
||||||
public async Task<ExchangeRates> GetRatesAsync()
|
public async Task<ExchangeRates> GetRatesAsync()
|
||||||
{
|
{
|
||||||
Used = true;
|
|
||||||
foreach (var p in _Providers)
|
foreach (var p in _Providers)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
|
|||||||
28
BTCPayServer/Services/Rates/NullRateProvider.cs
Normal file
28
BTCPayServer/Services/Rates/NullRateProvider.cs
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using BTCPayServer.Rating;
|
||||||
|
|
||||||
|
namespace BTCPayServer.Services.Rates
|
||||||
|
{
|
||||||
|
public class NullRateProvider : IRateProvider
|
||||||
|
{
|
||||||
|
private NullRateProvider()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
private static readonly NullRateProvider _Instance = new NullRateProvider();
|
||||||
|
public static NullRateProvider Instance
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return _Instance;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public Task<ExchangeRates> GetRatesAsync()
|
||||||
|
{
|
||||||
|
return Task.FromResult(new ExchangeRates());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -24,7 +24,7 @@ namespace BTCPayServer.Services.Rates
|
|||||||
public string EvaluatedRule { get; set; }
|
public string EvaluatedRule { get; set; }
|
||||||
public HashSet<RateRulesErrors> Errors { get; set; }
|
public HashSet<RateRulesErrors> Errors { get; set; }
|
||||||
public BidAsk BidAsk { get; set; }
|
public BidAsk BidAsk { get; set; }
|
||||||
public bool Cached { get; internal set; }
|
public TimeSpan Latency { get; internal set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public class RateFetcher
|
public class RateFetcher
|
||||||
@@ -72,13 +72,12 @@ namespace BTCPayServer.Services.Rates
|
|||||||
private async Task<RateResult> GetRuleValue(List<Task<QueryRateResult>> dependentQueries, RateRule rateRule)
|
private async Task<RateResult> GetRuleValue(List<Task<QueryRateResult>> dependentQueries, RateRule rateRule)
|
||||||
{
|
{
|
||||||
var result = new RateResult();
|
var result = new RateResult();
|
||||||
result.Cached = true;
|
|
||||||
foreach (var queryAsync in dependentQueries)
|
foreach (var queryAsync in dependentQueries)
|
||||||
{
|
{
|
||||||
var query = await queryAsync;
|
var query = await queryAsync;
|
||||||
if (!query.CachedResult)
|
result.Latency = query.Latency;
|
||||||
result.Cached = false;
|
if (query.Exception != null)
|
||||||
result.ExchangeExceptions.AddRange(query.Exceptions);
|
result.ExchangeExceptions.Add(query.Exception);
|
||||||
foreach (var rule in query.ExchangeRates)
|
foreach (var rule in query.ExchangeRates)
|
||||||
{
|
{
|
||||||
rateRule.ExchangeRates.SetRate(rule.Exchange, rule.CurrencyPair, rule.BidAsk);
|
rateRule.ExchangeRates.SetRate(rule.Exchange, rule.CurrencyPair, rule.BidAsk);
|
||||||
|
|||||||
@@ -12,11 +12,38 @@ namespace BTCPayServer.Services.Rates
|
|||||||
{
|
{
|
||||||
public class RateProviderFactory
|
public class RateProviderFactory
|
||||||
{
|
{
|
||||||
|
class WrapperRateProvider : IRateProvider
|
||||||
|
{
|
||||||
|
private readonly IRateProvider _inner;
|
||||||
|
public Exception Exception { get; private set; }
|
||||||
|
public TimeSpan Latency { get; set; }
|
||||||
|
public WrapperRateProvider(IRateProvider inner)
|
||||||
|
{
|
||||||
|
_inner = inner;
|
||||||
|
}
|
||||||
|
public Task<ExchangeRates> GetRatesAsync()
|
||||||
|
{
|
||||||
|
DateTimeOffset now = DateTimeOffset.UtcNow;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return _inner.GetRatesAsync();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Exception = ex;
|
||||||
|
return Task.FromResult(new ExchangeRates());
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
Latency = DateTimeOffset.UtcNow - now;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
public class QueryRateResult
|
public class QueryRateResult
|
||||||
{
|
{
|
||||||
public bool CachedResult { get; set; }
|
public TimeSpan Latency { get; set; }
|
||||||
public List<ExchangeException> Exceptions { get; set; }
|
|
||||||
public ExchangeRates ExchangeRates { get; set; }
|
public ExchangeRates ExchangeRates { get; set; }
|
||||||
|
public ExchangeException Exception { get; internal set; }
|
||||||
}
|
}
|
||||||
public RateProviderFactory(IOptions<MemoryCacheOptions> cacheOptions,
|
public RateProviderFactory(IOptions<MemoryCacheOptions> cacheOptions,
|
||||||
IHttpClientFactory httpClientFactory,
|
IHttpClientFactory httpClientFactory,
|
||||||
@@ -24,22 +51,12 @@ namespace BTCPayServer.Services.Rates
|
|||||||
{
|
{
|
||||||
_httpClientFactory = httpClientFactory;
|
_httpClientFactory = httpClientFactory;
|
||||||
_CoinAverageSettings = coinAverageSettings;
|
_CoinAverageSettings = coinAverageSettings;
|
||||||
_Cache = new MemoryCache(cacheOptions);
|
|
||||||
_CacheOptions = cacheOptions;
|
_CacheOptions = cacheOptions;
|
||||||
// We use 15 min because of limits with free version of bitcoinaverage
|
// We use 15 min because of limits with free version of bitcoinaverage
|
||||||
CacheSpan = TimeSpan.FromMinutes(15.0);
|
CacheSpan = TimeSpan.FromMinutes(15.0);
|
||||||
InitExchanges();
|
InitExchanges();
|
||||||
}
|
}
|
||||||
IMemoryCache _Cache;
|
|
||||||
private IOptions<MemoryCacheOptions> _CacheOptions;
|
private IOptions<MemoryCacheOptions> _CacheOptions;
|
||||||
|
|
||||||
public IMemoryCache Cache
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
return _Cache;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
TimeSpan _CacheSpan;
|
TimeSpan _CacheSpan;
|
||||||
public TimeSpan CacheSpan
|
public TimeSpan CacheSpan
|
||||||
{
|
{
|
||||||
@@ -55,12 +72,19 @@ namespace BTCPayServer.Services.Rates
|
|||||||
}
|
}
|
||||||
public void InvalidateCache()
|
public void InvalidateCache()
|
||||||
{
|
{
|
||||||
_Cache = new MemoryCache(_CacheOptions);
|
var cache = new MemoryCache(_CacheOptions);
|
||||||
|
foreach (var provider in Providers.Select(p => p.Value as CachedRateProvider).Where(p => p != null))
|
||||||
|
{
|
||||||
|
provider.CacheSpan = CacheSpan;
|
||||||
|
provider.MemoryCache = cache;
|
||||||
|
}
|
||||||
|
if (Providers.TryGetValue(CoinAverageRateProvider.CoinAverageName, out var coinAverage) && coinAverage is BackgroundFetcherRateProvider c)
|
||||||
|
c.RefreshRate = CacheSpan;
|
||||||
}
|
}
|
||||||
CoinAverageSettings _CoinAverageSettings;
|
CoinAverageSettings _CoinAverageSettings;
|
||||||
private readonly IHttpClientFactory _httpClientFactory;
|
private readonly IHttpClientFactory _httpClientFactory;
|
||||||
private readonly Dictionary<string, IRateProvider> _DirectProviders = new Dictionary<string, IRateProvider>();
|
private readonly Dictionary<string, IRateProvider> _DirectProviders = new Dictionary<string, IRateProvider>();
|
||||||
public Dictionary<string, IRateProvider> DirectProviders
|
public Dictionary<string, IRateProvider> Providers
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
@@ -71,24 +95,50 @@ namespace BTCPayServer.Services.Rates
|
|||||||
private void InitExchanges()
|
private void InitExchanges()
|
||||||
{
|
{
|
||||||
// We need to be careful to only add exchanges which OnGetTickers implementation make only 1 request
|
// We need to be careful to only add exchanges which OnGetTickers implementation make only 1 request
|
||||||
DirectProviders.Add("binance", new ExchangeSharpRateProvider("binance", new ExchangeBinanceAPI(), true));
|
Providers.Add("binance", new ExchangeSharpRateProvider("binance", new ExchangeBinanceAPI(), true));
|
||||||
DirectProviders.Add("bittrex", new ExchangeSharpRateProvider("bittrex", new ExchangeBittrexAPI(), true));
|
Providers.Add("bittrex", new ExchangeSharpRateProvider("bittrex", new ExchangeBittrexAPI(), true));
|
||||||
DirectProviders.Add("poloniex", new ExchangeSharpRateProvider("poloniex", new ExchangePoloniexAPI(), true));
|
Providers.Add("poloniex", new ExchangeSharpRateProvider("poloniex", new ExchangePoloniexAPI(), true));
|
||||||
DirectProviders.Add("hitbtc", new ExchangeSharpRateProvider("hitbtc", new ExchangeHitbtcAPI(), false));
|
Providers.Add("hitbtc", new ExchangeSharpRateProvider("hitbtc", new ExchangeHitbtcAPI(), false));
|
||||||
DirectProviders.Add("cryptopia", new ExchangeSharpRateProvider("cryptopia", new ExchangeCryptopiaAPI(), false));
|
Providers.Add("cryptopia", new ExchangeSharpRateProvider("cryptopia", new ExchangeCryptopiaAPI(), false));
|
||||||
|
|
||||||
// Handmade providers
|
// Handmade providers
|
||||||
DirectProviders.Add("bitpay", new BitpayRateProvider(new NBitpayClient.Bitpay(new NBitcoin.Key(), new Uri("https://bitpay.com/"))));
|
Providers.Add("bitpay", new BitpayRateProvider(new NBitpayClient.Bitpay(new NBitcoin.Key(), new Uri("https://bitpay.com/"))));
|
||||||
DirectProviders.Add(QuadrigacxRateProvider.QuadrigacxName, new QuadrigacxRateProvider());
|
Providers.Add(QuadrigacxRateProvider.QuadrigacxName, new QuadrigacxRateProvider());
|
||||||
DirectProviders.Add(CoinAverageRateProvider.CoinAverageName, new CoinAverageRateProvider() { Exchange = CoinAverageRateProvider.CoinAverageName, HttpClient = _httpClientFactory?.CreateClient(), Authenticator = _CoinAverageSettings });
|
Providers.Add(CoinAverageRateProvider.CoinAverageName, new CoinAverageRateProvider() { Exchange = CoinAverageRateProvider.CoinAverageName, HttpClient = _httpClientFactory?.CreateClient(), Authenticator = _CoinAverageSettings });
|
||||||
|
Providers.Add("kraken", new KrakenExchangeRateProvider() { HttpClient = _httpClientFactory?.CreateClient() });
|
||||||
|
|
||||||
// Those exchanges make multiple requests when calling GetTickers so we remove them
|
// Those exchanges make multiple requests when calling GetTickers so we remove them
|
||||||
DirectProviders.Add("kraken", new KrakenExchangeRateProvider() { HttpClient = _httpClientFactory?.CreateClient() });
|
|
||||||
//DirectProviders.Add("gdax", new ExchangeSharpRateProvider("gdax", new ExchangeGdaxAPI()));
|
//DirectProviders.Add("gdax", new ExchangeSharpRateProvider("gdax", new ExchangeGdaxAPI()));
|
||||||
//DirectProviders.Add("gemini", new ExchangeSharpRateProvider("gemini", new ExchangeGeminiAPI()));
|
//DirectProviders.Add("gemini", new ExchangeSharpRateProvider("gemini", new ExchangeGeminiAPI()));
|
||||||
//DirectProviders.Add("bitfinex", new ExchangeSharpRateProvider("bitfinex", new ExchangeBitfinexAPI()));
|
//DirectProviders.Add("bitfinex", new ExchangeSharpRateProvider("bitfinex", new ExchangeBitfinexAPI()));
|
||||||
//DirectProviders.Add("okex", new ExchangeSharpRateProvider("okex", new ExchangeOkexAPI()));
|
//DirectProviders.Add("okex", new ExchangeSharpRateProvider("okex", new ExchangeOkexAPI()));
|
||||||
//DirectProviders.Add("bitstamp", new ExchangeSharpRateProvider("bitstamp", new ExchangeBitstampAPI()));
|
//DirectProviders.Add("bitstamp", new ExchangeSharpRateProvider("bitstamp", new ExchangeBitstampAPI()));
|
||||||
|
|
||||||
|
foreach (var provider in Providers.ToArray())
|
||||||
|
{
|
||||||
|
var prov = new BackgroundFetcherRateProvider(Providers[provider.Key]);
|
||||||
|
prov.RefreshRate = provider.Key == CoinAverageRateProvider.CoinAverageName ? CacheSpan : TimeSpan.FromMinutes(1.0);
|
||||||
|
Providers[provider.Key] = prov;
|
||||||
|
}
|
||||||
|
|
||||||
|
var cache = new MemoryCache(_CacheOptions);
|
||||||
|
foreach (var supportedExchange in GetSupportedExchanges())
|
||||||
|
{
|
||||||
|
if (!Providers.ContainsKey(supportedExchange.Key))
|
||||||
|
{
|
||||||
|
var coinAverage = new CoinAverageRateProvider()
|
||||||
|
{
|
||||||
|
Exchange = supportedExchange.Key,
|
||||||
|
HttpClient = _httpClientFactory?.CreateClient(),
|
||||||
|
Authenticator = _CoinAverageSettings
|
||||||
|
};
|
||||||
|
var cached = new CachedRateProvider(supportedExchange.Key, coinAverage, cache)
|
||||||
|
{
|
||||||
|
CacheSpan = CacheSpan
|
||||||
|
};
|
||||||
|
Providers.Add(supportedExchange.Key, cached);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public CoinAverageExchanges GetSupportedExchanges()
|
public CoinAverageExchanges GetSupportedExchanges()
|
||||||
@@ -106,33 +156,18 @@ namespace BTCPayServer.Services.Rates
|
|||||||
return exchanges;
|
return exchanges;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool UseCoinAverageAsFallback { get; set; } = true;
|
|
||||||
public async Task<QueryRateResult> QueryRates(string exchangeName)
|
public async Task<QueryRateResult> QueryRates(string exchangeName)
|
||||||
{
|
{
|
||||||
List<IRateProvider> providers = new List<IRateProvider>();
|
Providers.TryGetValue(exchangeName, out var directProvider);
|
||||||
if (DirectProviders.TryGetValue(exchangeName, out var directProvider))
|
directProvider = directProvider ?? NullRateProvider.Instance;
|
||||||
providers.Add(directProvider);
|
|
||||||
if (UseCoinAverageAsFallback && _CoinAverageSettings.AvailableExchanges.ContainsKey(exchangeName))
|
var wrapper = new WrapperRateProvider(directProvider);
|
||||||
{
|
var value = await wrapper.GetRatesAsync();
|
||||||
providers.Add(new CoinAverageRateProvider()
|
|
||||||
{
|
|
||||||
Exchange = exchangeName,
|
|
||||||
HttpClient = _httpClientFactory?.CreateClient(),
|
|
||||||
Authenticator = _CoinAverageSettings
|
|
||||||
});
|
|
||||||
}
|
|
||||||
var fallback = new FallbackRateProvider(providers.ToArray());
|
|
||||||
var cached = new CachedRateProvider(exchangeName, fallback, _Cache)
|
|
||||||
{
|
|
||||||
CacheSpan = CacheSpan
|
|
||||||
};
|
|
||||||
var value = await cached.GetRatesAsync();
|
|
||||||
return new QueryRateResult()
|
return new QueryRateResult()
|
||||||
{
|
{
|
||||||
CachedResult = !fallback.Used,
|
Latency = wrapper.Latency,
|
||||||
ExchangeRates = value,
|
ExchangeRates = value,
|
||||||
Exceptions = fallback.Exceptions
|
Exception = wrapper.Exception != null ? new ExchangeException() { Exception = wrapper.Exception, ExchangeName = exchangeName } : null
|
||||||
.Select(c => new ExchangeException() { Exception = c, ExchangeName = exchangeName }).ToList()
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -83,7 +83,7 @@ namespace BTCPayServer.Services
|
|||||||
MultiValueDictionary<Type, TaskCompletionSource<bool>> _Subscriptions = new MultiValueDictionary<Type, TaskCompletionSource<bool>>();
|
MultiValueDictionary<Type, TaskCompletionSource<bool>> _Subscriptions = new MultiValueDictionary<Type, TaskCompletionSource<bool>>();
|
||||||
public async Task WaitSettingsChanged<T>(CancellationToken cancellation)
|
public async Task WaitSettingsChanged<T>(CancellationToken cancellation)
|
||||||
{
|
{
|
||||||
var tcs = new TaskCompletionSource<bool>();
|
var tcs = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||||
using (cancellation.Register(() =>
|
using (cancellation.Register(() =>
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
|
|||||||
Reference in New Issue
Block a user