diff --git a/BTCPayServer.Tests/docker-compose.yml b/BTCPayServer.Tests/docker-compose.yml
index 033943367..c0e1f3703 100644
--- a/BTCPayServer.Tests/docker-compose.yml
+++ b/BTCPayServer.Tests/docker-compose.yml
@@ -39,7 +39,7 @@ services:
- postgres
bitcoin-nbxplorer:
- image: nicolasdorier/nbxplorer:1.0.0.45
+ image: nicolasdorier/nbxplorer:1.0.0.47
ports:
- "32838:32838"
expose:
@@ -57,7 +57,7 @@ services:
- bitcoind
litecoin-nbxplorer:
- image: nicolasdorier/nbxplorer:1.0.0.45
+ image: nicolasdorier/nbxplorer:1.0.0.47
ports:
- "32839:32839"
expose:
diff --git a/BTCPayServer/BTCPayServer.csproj b/BTCPayServer/BTCPayServer.csproj
index 134efd480..a741f33c4 100644
--- a/BTCPayServer/BTCPayServer.csproj
+++ b/BTCPayServer/BTCPayServer.csproj
@@ -24,7 +24,7 @@
-
+
diff --git a/BTCPayServer/HostedServices/InvoiceWatcher.cs b/BTCPayServer/HostedServices/InvoiceWatcher.cs
index 4095a9ded..f857233fa 100644
--- a/BTCPayServer/HostedServices/InvoiceWatcher.cs
+++ b/BTCPayServer/HostedServices/InvoiceWatcher.cs
@@ -17,6 +17,7 @@ using BTCPayServer.Controllers;
using BTCPayServer.Events;
using Microsoft.AspNetCore.Hosting;
using BTCPayServer.Services.Invoices;
+using BTCPayServer.Services;
namespace BTCPayServer.HostedServices
{
@@ -150,7 +151,7 @@ namespace BTCPayServer.HostedServices
}
var derivationStrategies = invoice.GetDerivationStrategies(_NetworkProvider).ToArray();
- var payments = await GetPaymentsWithTransaction(null, derivationStrategies, invoice);
+ var payments = await GetPaymentsWithTransaction(derivationStrategies, invoice);
foreach (Task coinsAsync in GetCoinsPerNetwork(context, invoice, derivationStrategies))
{
var coins = await coinsAsync;
@@ -173,7 +174,7 @@ namespace BTCPayServer.HostedServices
}
if (dirtyAddress)
{
- payments = await GetPaymentsWithTransaction(payments, derivationStrategies, invoice);
+ payments = await GetPaymentsWithTransaction(derivationStrategies, invoice);
}
var network = coins.Wallet.Network;
var cryptoData = invoice.GetCryptoData(network, _NetworkProvider);
@@ -293,58 +294,23 @@ namespace BTCPayServer.HostedServices
}
- class AccountedPaymentEntities : List
- {
- public AccountedPaymentEntities(AccountedPaymentEntities existing)
- {
- if (existing != null)
- _Transactions = existing._Transactions;
- }
-
- Dictionary _Transactions = new Dictionary();
-
- public void AddToCache(IEnumerable transactions)
- {
- foreach (var tx in transactions)
- _Transactions.TryAdd(tx.Transaction.GetHash(), tx);
- }
- public TransactionResult GetTransaction(uint256 txId)
- {
- _Transactions.TryGetValue(txId, out TransactionResult result);
- return result;
- }
-
- internal IEnumerable GetTransactions()
- {
- return _Transactions.Values;
- }
- }
- private async Task GetPaymentsWithTransaction(AccountedPaymentEntities previous, DerivationStrategy[] derivations, InvoiceEntity invoice)
+ private async Task> GetPaymentsWithTransaction(DerivationStrategy[] derivations, InvoiceEntity invoice)
{
List updatedPaymentEntities = new List();
- AccountedPaymentEntities accountedPayments = new AccountedPaymentEntities(previous);
+ List accountedPayments = new List();
foreach (var network in derivations.Select(d => d.Network))
{
var wallet = _WalletProvider.GetWallet(network);
if (wallet == null)
continue;
- var hashesToFetch = new HashSet(invoice
- .GetPayments(network)
+ var transactions = await wallet.GetTransactions(invoice.GetPayments(wallet.Network)
.Select(t => t.Outpoint.Hash)
- .Where(h => accountedPayments?.GetTransaction(h) == null)
- .ToList());
-
-
- if (hashesToFetch.Count > 0)
- {
- accountedPayments.AddToCache((await wallet.GetTransactions(hashesToFetch.ToArray())).Select(t => t.Value));
- }
- var conflicts = GetConflicts(accountedPayments.GetTransactions());
+ .ToArray());
+ var conflicts = GetConflicts(transactions.Select(t => t.Value));
foreach (var payment in invoice.GetPayments(network))
{
- TransactionResult tx = accountedPayments.GetTransaction(payment.Outpoint.Hash);
- if (tx == null)
+ if (!transactions.TryGetValue(payment.Outpoint.Hash, out TransactionResult tx))
continue;
AccountedPaymentEntity accountedPayment = new AccountedPaymentEntity()
@@ -370,7 +336,6 @@ namespace BTCPayServer.HostedServices
return accountedPayments;
}
-
class TransactionConflict
{
public Dictionary Transactions { get; set; } = new Dictionary();
diff --git a/BTCPayServer/HostedServices/NBXplorerListener.cs b/BTCPayServer/HostedServices/NBXplorerListener.cs
index 329f89198..52687b399 100644
--- a/BTCPayServer/HostedServices/NBXplorerListener.cs
+++ b/BTCPayServer/HostedServices/NBXplorerListener.cs
@@ -12,6 +12,7 @@ using NBXplorer;
using System.Collections.Concurrent;
using NBXplorer.DerivationStrategy;
using BTCPayServer.Events;
+using BTCPayServer.Services;
namespace BTCPayServer.HostedServices
{
@@ -24,9 +25,11 @@ namespace BTCPayServer.HostedServices
private TaskCompletionSource _RunningTask;
private CancellationTokenSource _Cts;
NBXplorerDashboard _Dashboards;
+ TransactionCacheProvider _TxCache;
public NBXplorerListener(ExplorerClientProvider explorerClients,
NBXplorerDashboard dashboard,
+ TransactionCacheProvider cacheProvider,
InvoiceRepository invoiceRepository,
EventAggregator aggregator, IApplicationLifetime lifetime)
{
@@ -36,6 +39,7 @@ namespace BTCPayServer.HostedServices
_ExplorerClients = explorerClients;
_Aggregator = aggregator;
_Lifetime = lifetime;
+ _TxCache = cacheProvider;
}
CompositeDisposable leases = new CompositeDisposable();
@@ -130,11 +134,13 @@ namespace BTCPayServer.HostedServices
switch (newEvent)
{
case NBXplorer.Models.NewBlockEvent evt:
+ _TxCache.GetTransactionCache(network).NewBlock(evt.Hash, evt.PreviousBlockHash);
_Aggregator.Publish(new Events.NewBlockEvent());
break;
case NBXplorer.Models.NewTransactionEvent evt:
- foreach (var txout in evt.Match.Outputs)
+ foreach (var txout in evt.Outputs)
{
+ _TxCache.GetTransactionCache(network).AddToCache(evt.TransactionData);
_Aggregator.Publish(new Events.TxOutReceivedEvent()
{
Network = network,
diff --git a/BTCPayServer/Hosting/BTCPayServerServices.cs b/BTCPayServer/Hosting/BTCPayServerServices.cs
index c47024ff0..698ca93f9 100644
--- a/BTCPayServer/Hosting/BTCPayServerServices.cs
+++ b/BTCPayServer/Hosting/BTCPayServerServices.cs
@@ -142,6 +142,8 @@ namespace BTCPayServer.Hosting
BlockTarget = 20
});
+ services.AddSingleton();
+
services.AddSingleton();
services.AddSingleton();
services.AddSingleton();
diff --git a/BTCPayServer/Services/TransactionCache.cs b/BTCPayServer/Services/TransactionCache.cs
new file mode 100644
index 000000000..c91f33eb7
--- /dev/null
+++ b/BTCPayServer/Services/TransactionCache.cs
@@ -0,0 +1,92 @@
+using System;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.Extensions.Caching.Memory;
+using Microsoft.Extensions.Options;
+using NBitcoin;
+using NBXplorer.Models;
+
+namespace BTCPayServer.Services
+{
+ public class TransactionCacheProvider
+ {
+ IOptions _Options;
+ public TransactionCacheProvider(IOptions options)
+ {
+ _Options = options;
+ }
+
+ ConcurrentDictionary _TransactionCaches = new ConcurrentDictionary();
+ public TransactionCache GetTransactionCache(BTCPayNetwork network)
+ {
+ if (network == null)
+ throw new ArgumentNullException(nameof(network));
+ return _TransactionCaches.GetOrAdd(network.CryptoCode, c => new TransactionCache(_Options, network));
+ }
+ }
+ public class TransactionCache : IDisposable
+ {
+ IOptions _Options;
+ public TransactionCache(IOptions options, BTCPayNetwork network)
+ {
+ if (network == null)
+ throw new ArgumentNullException(nameof(network));
+ _Options = options;
+ _MemoryCache = new MemoryCache(_Options);
+ Network = network;
+ }
+
+ uint256 _LastHash;
+ int _ConfOffset;
+ IMemoryCache _MemoryCache;
+
+ public void NewBlock(uint256 newHash, uint256 previousHash)
+ {
+ if (_LastHash != previousHash)
+ {
+ var old = _MemoryCache;
+ _ConfOffset = 0;
+ _MemoryCache = new MemoryCache(_Options);
+ Thread.MemoryBarrier();
+ old.Dispose();
+ }
+ else
+ _ConfOffset++;
+ _LastHash = newHash;
+ }
+
+ public TimeSpan CacheSpan { get; private set; } = TimeSpan.FromMinutes(60);
+
+ public BTCPayNetwork Network { get; private set; }
+
+ public void AddToCache(TransactionResult tx)
+ {
+ _MemoryCache.Set(tx.Transaction.GetHash(), tx, DateTimeOffset.UtcNow + CacheSpan);
+ }
+
+
+ public TransactionResult GetTransaction(uint256 txId)
+ {
+ _MemoryCache.TryGetValue(txId.ToString(), out object tx);
+
+ var result = tx as TransactionResult;
+ var confOffset = _ConfOffset;
+ if (result != null && result.Confirmations > 0 && confOffset > 0)
+ {
+ var serializer = new NBXplorer.Serializer(Network.NBitcoinNetwork);
+ result = serializer.ToObject(serializer.ToString(result));
+ result.Confirmations += confOffset;
+ result.Height += confOffset;
+ }
+ return result;
+ }
+
+ public void Dispose()
+ {
+ _MemoryCache.Dispose();
+ }
+ }
+}
diff --git a/BTCPayServer/Services/Wallets/BTCPayWallet.cs b/BTCPayServer/Services/Wallets/BTCPayWallet.cs
index f8808587c..24c7e13f4 100644
--- a/BTCPayServer/Services/Wallets/BTCPayWallet.cs
+++ b/BTCPayServer/Services/Wallets/BTCPayWallet.cs
@@ -33,13 +33,14 @@ namespace BTCPayServer.Services.Wallets
public class BTCPayWallet
{
private ExplorerClient _Client;
-
- public BTCPayWallet(ExplorerClient client, BTCPayNetwork network)
+ private TransactionCache _Cache;
+ public BTCPayWallet(ExplorerClient client, TransactionCache cache, BTCPayNetwork network)
{
if (client == null)
throw new ArgumentNullException(nameof(client));
_Client = client;
_Network = network;
+ _Cache = cache;
}
@@ -65,11 +66,16 @@ namespace BTCPayServer.Services.Wallets
await _Client.TrackAsync(derivationStrategy);
}
- public Task GetTransactionAsync(uint256 txId, CancellationToken cancellation = default(CancellationToken))
+ public async Task GetTransactionAsync(uint256 txId, CancellationToken cancellation = default(CancellationToken))
{
if (txId == null)
throw new ArgumentNullException(nameof(txId));
- return _Client.GetTransactionAsync(txId, cancellation);
+ var tx = _Cache.GetTransaction(txId);
+ if (tx != null)
+ return tx;
+ tx = await _Client.GetTransactionAsync(txId, cancellation);
+ _Cache.AddToCache(tx);
+ return tx;
}
public async Task GetCoins(DerivationStrategyBase strategy, KnownState state, CancellationToken cancellation = default(CancellationToken))
diff --git a/BTCPayServer/Services/Wallets/BTCPayWalletProvider.cs b/BTCPayServer/Services/Wallets/BTCPayWalletProvider.cs
index 7b8e46756..0c1971a4f 100644
--- a/BTCPayServer/Services/Wallets/BTCPayWalletProvider.cs
+++ b/BTCPayServer/Services/Wallets/BTCPayWalletProvider.cs
@@ -10,11 +10,15 @@ namespace BTCPayServer.Services.Wallets
{
private ExplorerClientProvider _Client;
BTCPayNetworkProvider _NetworkProvider;
- public BTCPayWalletProvider(ExplorerClientProvider client, BTCPayNetworkProvider networkProvider)
+ TransactionCacheProvider _TransactionCacheProvider;
+ public BTCPayWalletProvider(ExplorerClientProvider client,
+ TransactionCacheProvider transactionCacheProvider,
+ BTCPayNetworkProvider networkProvider)
{
if (client == null)
throw new ArgumentNullException(nameof(client));
_Client = client;
+ _TransactionCacheProvider = transactionCacheProvider;
_NetworkProvider = networkProvider;
}
@@ -32,7 +36,7 @@ namespace BTCPayServer.Services.Wallets
var client = _Client.GetExplorerClient(cryptoCode);
if (network == null || client == null)
return null;
- return new BTCPayWallet(client, network);
+ return new BTCPayWallet(client, _TransactionCacheProvider.GetTransactionCache(network), network);
}
}
}