Calculate rate properly per crypto

This commit is contained in:
nicolas.dorier
2018-01-09 02:57:06 +09:00
parent 31672a2587
commit 76d50b018b
12 changed files with 80 additions and 21 deletions

View File

@@ -459,11 +459,11 @@ namespace BTCPayServer.Tests
[Fact] [Fact]
public void CheckRatesProvider() public void CheckRatesProvider()
{ {
var coinAverage = new CoinAverageRateProvider(); var coinAverage = new CoinAverageRateProvider("BTC");
var jpy = coinAverage.GetRateAsync("JPY").GetAwaiter().GetResult(); var jpy = coinAverage.GetRateAsync("JPY").GetAwaiter().GetResult();
var jpy2 = new BitpayRateProvider(new Bitpay(new Key(), new Uri("https://bitpay.com/"))).GetRateAsync("JPY").GetAwaiter().GetResult(); var jpy2 = new BitpayRateProvider(new Bitpay(new Key(), new Uri("https://bitpay.com/"))).GetRateAsync("JPY").GetAwaiter().GetResult();
var cached = new CachedRateProvider(coinAverage, new MemoryCache(new MemoryCacheOptions() { ExpirationScanFrequency = TimeSpan.FromSeconds(1.0) })); var cached = new CachedRateProvider("BTC", coinAverage, new MemoryCache(new MemoryCacheOptions() { ExpirationScanFrequency = TimeSpan.FromSeconds(1.0) }));
cached.CacheSpan = TimeSpan.FromSeconds(10); cached.CacheSpan = TimeSpan.FromSeconds(10);
var a = cached.GetRateAsync("JPY").GetAwaiter().GetResult(); var a = cached.GetRateAsync("JPY").GetAwaiter().GetResult();
var b = cached.GetRateAsync("JPY").GetAwaiter().GetResult(); var b = cached.GetRateAsync("JPY").GetAwaiter().GetResult();

View File

@@ -2,6 +2,7 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using BTCPayServer.Services.Rates;
using NBitcoin; using NBitcoin;
namespace BTCPayServer namespace BTCPayServer
@@ -12,7 +13,7 @@ namespace BTCPayServer
public string CryptoCode { get; internal set; } public string CryptoCode { get; internal set; }
public string BlockExplorerLink { get; internal set; } public string BlockExplorerLink { get; internal set; }
public string UriScheme { get; internal set; } public string UriScheme { get; internal set; }
public IRateProvider DefaultRateProvider { get; set; }
[Obsolete("Should not be needed")] [Obsolete("Should not be needed")]
public bool IsBTC public bool IsBTC

View File

@@ -2,7 +2,10 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using BTCPayServer.Services.Rates;
using Microsoft.Extensions.Caching.Memory;
using NBitcoin; using NBitcoin;
using NBitpayClient;
namespace BTCPayServer namespace BTCPayServer
{ {
@@ -15,6 +18,11 @@ namespace BTCPayServer
Dictionary<string, BTCPayNetwork> _Networks = new Dictionary<string, BTCPayNetwork>(); Dictionary<string, BTCPayNetwork> _Networks = new Dictionary<string, BTCPayNetwork>();
public BTCPayNetworkProvider(Network network) public BTCPayNetworkProvider(Network network)
{ {
var coinaverage = new CoinAverageRateProvider("BTC");
var bitpay = new BitpayRateProvider(new Bitpay(new Key(), new Uri("https://bitpay.com/")));
var btcRate = new FallbackRateProvider(new IRateProvider[] { coinaverage, bitpay });
var ltcRate = new CoinAverageRateProvider("LTC");
if (network == Network.Main) if (network == Network.Main)
{ {
Add(new BTCPayNetwork() Add(new BTCPayNetwork()
@@ -23,6 +31,7 @@ namespace BTCPayServer
BlockExplorerLink = "https://www.smartbit.com.au/tx/{0}", BlockExplorerLink = "https://www.smartbit.com.au/tx/{0}",
NBitcoinNetwork = Network.Main, NBitcoinNetwork = Network.Main,
UriScheme = "bitcoin", UriScheme = "bitcoin",
DefaultRateProvider = btcRate
}); });
Add(new BTCPayNetwork() Add(new BTCPayNetwork()
{ {
@@ -30,6 +39,7 @@ namespace BTCPayServer
BlockExplorerLink = "https://live.blockcypher.com/ltc/tx/{0}/", BlockExplorerLink = "https://live.blockcypher.com/ltc/tx/{0}/",
NBitcoinNetwork = NBXplorer.Altcoins.Litecoin.Networks.Mainnet, NBitcoinNetwork = NBXplorer.Altcoins.Litecoin.Networks.Mainnet,
UriScheme = "litecoin", UriScheme = "litecoin",
DefaultRateProvider = ltcRate
}); });
} }
@@ -41,6 +51,7 @@ namespace BTCPayServer
BlockExplorerLink = "https://testnet.smartbit.com.au/tx/{0}", BlockExplorerLink = "https://testnet.smartbit.com.au/tx/{0}",
NBitcoinNetwork = Network.TestNet, NBitcoinNetwork = Network.TestNet,
UriScheme = "bitcoin", UriScheme = "bitcoin",
DefaultRateProvider = btcRate
}); });
Add(new BTCPayNetwork() Add(new BTCPayNetwork()
{ {
@@ -48,6 +59,7 @@ namespace BTCPayServer
BlockExplorerLink = "http://explorer.litecointools.com/tx/{0}", BlockExplorerLink = "http://explorer.litecointools.com/tx/{0}",
NBitcoinNetwork = NBXplorer.Altcoins.Litecoin.Networks.Testnet, NBitcoinNetwork = NBXplorer.Altcoins.Litecoin.Networks.Testnet,
UriScheme = "litecoin", UriScheme = "litecoin",
DefaultRateProvider = ltcRate
}); });
} }
@@ -58,7 +70,8 @@ namespace BTCPayServer
CryptoCode = "BTC", CryptoCode = "BTC",
BlockExplorerLink = "https://testnet.smartbit.com.au/tx/{0}", BlockExplorerLink = "https://testnet.smartbit.com.au/tx/{0}",
NBitcoinNetwork = Network.RegTest, NBitcoinNetwork = Network.RegTest,
UriScheme = "bitcoin" UriScheme = "bitcoin",
DefaultRateProvider = btcRate
}); });
Add(new BTCPayNetwork() Add(new BTCPayNetwork()
{ {
@@ -66,6 +79,7 @@ namespace BTCPayServer
BlockExplorerLink = "http://explorer.litecointools.com/tx/{0}", BlockExplorerLink = "http://explorer.litecointools.com/tx/{0}",
NBitcoinNetwork = NBXplorer.Altcoins.Litecoin.Networks.Regtest, NBitcoinNetwork = NBXplorer.Altcoins.Litecoin.Networks.Regtest,
UriScheme = "litecoin", UriScheme = "litecoin",
DefaultRateProvider = ltcRate
}); });
} }
} }

View File

@@ -46,7 +46,7 @@ namespace BTCPayServer.Controllers
{ {
InvoiceRepository _InvoiceRepository; InvoiceRepository _InvoiceRepository;
BTCPayWallet _Wallet; BTCPayWallet _Wallet;
IRateProvider _RateProvider; IRateProviderFactory _RateProviders;
StoreRepository _StoreRepository; StoreRepository _StoreRepository;
UserManager<ApplicationUser> _UserManager; UserManager<ApplicationUser> _UserManager;
IFeeProviderFactory _FeeProviderFactory; IFeeProviderFactory _FeeProviderFactory;
@@ -58,7 +58,7 @@ namespace BTCPayServer.Controllers
CurrencyNameTable currencyNameTable, CurrencyNameTable currencyNameTable,
UserManager<ApplicationUser> userManager, UserManager<ApplicationUser> userManager,
BTCPayWallet wallet, BTCPayWallet wallet,
IRateProvider rateProvider, IRateProviderFactory rateProviders,
StoreRepository storeRepository, StoreRepository storeRepository,
EventAggregator eventAggregator, EventAggregator eventAggregator,
BTCPayNetworkProvider networkProvider, BTCPayNetworkProvider networkProvider,
@@ -70,7 +70,7 @@ namespace BTCPayServer.Controllers
_StoreRepository = storeRepository ?? throw new ArgumentNullException(nameof(storeRepository)); _StoreRepository = storeRepository ?? throw new ArgumentNullException(nameof(storeRepository));
_InvoiceRepository = invoiceRepository ?? throw new ArgumentNullException(nameof(invoiceRepository)); _InvoiceRepository = invoiceRepository ?? throw new ArgumentNullException(nameof(invoiceRepository));
_Wallet = wallet ?? throw new ArgumentNullException(nameof(wallet)); _Wallet = wallet ?? throw new ArgumentNullException(nameof(wallet));
_RateProvider = rateProvider ?? throw new ArgumentNullException(nameof(rateProvider)); _RateProviders = rateProviders ?? throw new ArgumentNullException(nameof(rateProviders));
_UserManager = userManager; _UserManager = userManager;
_FeeProviderFactory = feeProviderFactory ?? throw new ArgumentNullException(nameof(feeProviderFactory)); _FeeProviderFactory = feeProviderFactory ?? throw new ArgumentNullException(nameof(feeProviderFactory));
_EventAggregator = eventAggregator; _EventAggregator = eventAggregator;
@@ -122,7 +122,7 @@ namespace BTCPayServer.Controllers
{ {
network = derivationStrategy.Network, network = derivationStrategy.Network,
getFeeRate = _FeeProviderFactory.CreateFeeProvider(derivationStrategy.Network).GetFeeRateAsync(), getFeeRate = _FeeProviderFactory.CreateFeeProvider(derivationStrategy.Network).GetFeeRateAsync(),
getRate = _RateProvider.GetRateAsync(invoice.Currency), getRate = _RateProviders.GetRateProvider(derivationStrategy.Network).GetRateAsync(invoice.Currency),
getAddress = _Wallet.ReserveAddressAsync(derivationStrategy) getAddress = _Wallet.ReserveAddressAsync(derivationStrategy)
}; };
}); });

View File

@@ -159,7 +159,7 @@ namespace BTCPayServer.HostedServices
var alreadyAccounted = new HashSet<OutPoint>(invoice.Payments.Select(p => p.Outpoint)); var alreadyAccounted = new HashSet<OutPoint>(invoice.Payments.Select(p => p.Outpoint));
foreach (var coin in coins.Coins.Where(c => !alreadyAccounted.Contains(c.Outpoint))) foreach (var coin in coins.Coins.Where(c => !alreadyAccounted.Contains(c.Outpoint)))
{ {
var payment = await _InvoiceRepository.AddPayment(invoice.Id, coin).ConfigureAwait(false); var payment = await _InvoiceRepository.AddPayment(invoice.Id, coin, coins.Strategy.Network.CryptoCode).ConfigureAwait(false);
invoice.Payments.Add(payment); invoice.Payments.Add(payment);
context.Events.Add(new InvoicePaymentEvent(invoice.Id)); context.Events.Add(new InvoicePaymentEvent(invoice.Id));
dirtyAddress = true; dirtyAddress = true;

View File

@@ -155,12 +155,7 @@ namespace BTCPayServer.Hosting
else else
return new Bitpay(new Key(), new Uri("https://test.bitpay.com/")); return new Bitpay(new Key(), new Uri("https://test.bitpay.com/"));
}); });
services.TryAddSingleton<IRateProvider>(o => services.TryAddSingleton<IRateProviderFactory, CachedDefaultRateProviderFactory>();
{
var coinaverage = new CoinAverageRateProvider();
var bitpay = new BitpayRateProvider(new Bitpay(new Key(), new Uri("https://bitpay.com/")));
return new CachedRateProvider(new FallbackRateProvider(new IRateProvider[] { coinaverage, bitpay }), o.GetRequiredService<IMemoryCache>()) { CacheSpan = TimeSpan.FromMinutes(1.0) };
});
services.TryAddScoped<IHttpContextAccessor, HttpContextAccessor>(); services.TryAddScoped<IHttpContextAccessor, HttpContextAccessor>();
services.TryAddSingleton<IAuthorizationHandler, OwnStoreHandler>(); services.TryAddSingleton<IAuthorizationHandler, OwnStoreHandler>();

View File

@@ -419,7 +419,7 @@ namespace BTCPayServer.Services.Invoices
AddToTextSearch(invoiceId, addresses.Select(a => a.ToString()).ToArray()); AddToTextSearch(invoiceId, addresses.Select(a => a.ToString()).ToArray());
} }
public async Task<PaymentEntity> AddPayment(string invoiceId, Coin receivedCoin) public async Task<PaymentEntity> AddPayment(string invoiceId, Coin receivedCoin, string cryptoCode)
{ {
using (var context = _ContextFactory.CreateContext()) using (var context = _ContextFactory.CreateContext())
{ {
@@ -428,6 +428,7 @@ namespace BTCPayServer.Services.Invoices
Outpoint = receivedCoin.Outpoint, Outpoint = receivedCoin.Outpoint,
#pragma warning disable CS0618 #pragma warning disable CS0618
Output = receivedCoin.TxOut, Output = receivedCoin.TxOut,
CryptoCode = cryptoCode,
#pragma warning restore CS0618 #pragma warning restore CS0618
ReceivedTime = DateTime.UtcNow ReceivedTime = DateTime.UtcNow
}; };

View File

@@ -0,0 +1,27 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.Extensions.Caching.Memory;
namespace BTCPayServer.Services.Rates
{
public class CachedDefaultRateProviderFactory : IRateProviderFactory
{
IMemoryCache _Cache;
ConcurrentDictionary<string, IRateProvider> _Providers = new ConcurrentDictionary<string, IRateProvider>();
public CachedDefaultRateProviderFactory(IMemoryCache cache)
{
if (cache == null)
throw new ArgumentNullException(nameof(cache));
_Cache = cache;
}
public TimeSpan CacheSpan { get; set; } = TimeSpan.FromMinutes(1.0);
public IRateProvider GetRateProvider(BTCPayNetwork network)
{
return _Providers.GetOrAdd(network.CryptoCode, new CachedRateProvider(network.CryptoCode, network.DefaultRateProvider, _Cache) { CacheSpan = CacheSpan });
}
}
}

View File

@@ -10,8 +10,9 @@ namespace BTCPayServer.Services.Rates
{ {
private IRateProvider _Inner; private IRateProvider _Inner;
private IMemoryCache _MemoryCache; private IMemoryCache _MemoryCache;
private string _CryptoCode;
public CachedRateProvider(IRateProvider inner, IMemoryCache memoryCache) public CachedRateProvider(string cryptoCode, IRateProvider inner, IMemoryCache memoryCache)
{ {
if (inner == null) if (inner == null)
throw new ArgumentNullException(nameof(inner)); throw new ArgumentNullException(nameof(inner));
@@ -19,6 +20,7 @@ namespace BTCPayServer.Services.Rates
throw new ArgumentNullException(nameof(memoryCache)); throw new ArgumentNullException(nameof(memoryCache));
this._Inner = inner; this._Inner = inner;
this._MemoryCache = memoryCache; this._MemoryCache = memoryCache;
this._CryptoCode = cryptoCode;
} }
public TimeSpan CacheSpan public TimeSpan CacheSpan
@@ -29,7 +31,7 @@ namespace BTCPayServer.Services.Rates
public Task<decimal> GetRateAsync(string currency) public Task<decimal> GetRateAsync(string currency)
{ {
return _MemoryCache.GetOrCreateAsync("CURR_" + currency, (ICacheEntry entry) => return _MemoryCache.GetOrCreateAsync("CURR_" + currency + "_" + _CryptoCode, (ICacheEntry entry) =>
{ {
entry.AbsoluteExpiration = DateTimeOffset.UtcNow + CacheSpan; entry.AbsoluteExpiration = DateTimeOffset.UtcNow + CacheSpan;
return _Inner.GetRateAsync(currency); return _Inner.GetRateAsync(currency);

View File

@@ -19,6 +19,13 @@ namespace BTCPayServer.Services.Rates
{ {
static HttpClient _Client = new HttpClient(); static HttpClient _Client = new HttpClient();
public CoinAverageRateProvider(string cryptoCode)
{
CryptoCode = cryptoCode ?? "BTC";
}
public string CryptoCode { get; set; }
public string Market public string Market
{ {
get; set; get; set;
@@ -51,8 +58,8 @@ namespace BTCPayServer.Services.Rates
resp.EnsureSuccessStatusCode(); resp.EnsureSuccessStatusCode();
var rates = JObject.Parse(await resp.Content.ReadAsStringAsync()); var rates = JObject.Parse(await resp.Content.ReadAsStringAsync());
return rates.Properties() return rates.Properties()
.Where(p => p.Name.StartsWith("BTC", StringComparison.OrdinalIgnoreCase)) .Where(p => p.Name.StartsWith(CryptoCode, StringComparison.OrdinalIgnoreCase))
.ToDictionary(p => p.Name.Substring(3, 3), p => ToDecimal(p.Value["last"])); .ToDictionary(p => p.Name.Substring(CryptoCode.Length, p.Name.Length - CryptoCode.Length), p => ToDecimal(p.Value["last"]));
} }
} }

View File

@@ -0,0 +1,12 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace BTCPayServer.Services.Rates
{
public interface IRateProviderFactory
{
IRateProvider GetRateProvider(BTCPayNetwork network);
}
}

View File

@@ -207,7 +207,7 @@
{ {
<tr> <tr>
<td>@address.GetCryptoCode()</td> <td>@address.GetCryptoCode()</td>
<td>@address.Address</td> <td>@address.GetAddress()</td>
<td>@(!address.UnAssigned.HasValue)</td> <td>@(!address.UnAssigned.HasValue)</td>
</tr> </tr>
} }