Compare commits

...

3 Commits

Author SHA1 Message Date
01d898b618 Caching GetCoins 2018-02-15 13:02:12 +09:00
17069c311b Remove transaction cache 2018-02-15 12:42:48 +09:00
921d072942 fix electrum format for add derivation 2018-02-15 11:47:45 +09:00
9 changed files with 139 additions and 238 deletions

View File

@ -2,7 +2,7 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp2.0</TargetFramework>
<Version>1.0.1.25</Version>
<Version>1.0.1.27</Version>
<NoWarn>NU1701</NoWarn>
</PropertyGroup>
<ItemGroup>

View File

@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using NBitcoin;
using NBXplorer.DerivationStrategy;
namespace BTCPayServer.Events
{
@ -10,6 +11,7 @@ namespace BTCPayServer.Events
{
public BTCPayNetwork Network { get; set; }
public Script ScriptPubKey { get; set; }
public DerivationStrategyBase DerivationStrategy { get; set; }
public override string ToString()
{

View File

@ -29,9 +29,6 @@ namespace BTCPayServer.HostedServices
{
}
public Dictionary<BTCPayNetwork, KnownState> KnownStates { get; set; }
public Dictionary<BTCPayNetwork, KnownState> ModifiedKnownStates { get; set; } = new Dictionary<BTCPayNetwork, KnownState>();
public InvoiceEntity Invoice { get; set; }
public List<object> Events { get; set; } = new List<object>();
@ -64,14 +61,15 @@ namespace BTCPayServer.HostedServices
}
CompositeDisposable leases = new CompositeDisposable();
async Task NotifyReceived(Script scriptPubKey, BTCPayNetwork network)
async Task NotifyReceived(Events.TxOutReceivedEvent evt)
{
var invoice = await _InvoiceRepository.GetInvoiceIdFromScriptPubKey(scriptPubKey, network.CryptoCode);
if (invoice != null)
var invoiceId = await _InvoiceRepository.GetInvoiceIdFromScriptPubKey(evt.ScriptPubKey, evt.Network.CryptoCode);
if (invoiceId != null)
{
String address = scriptPubKey.GetDestinationAddress(network.NBitcoinNetwork)?.ToString() ?? scriptPubKey.ToString();
Logs.PayServer.LogInformation($"{address} is mapping to invoice {invoice}");
_WatchRequests.Add(invoice);
String address = evt.ScriptPubKey.GetDestinationAddress(evt.Network.NBitcoinNetwork)?.ToString() ?? evt.ScriptPubKey.ToString();
_WalletProvider.GetWallet(evt.Network).InvalidateCache(evt.DerivationStrategy);
Logs.PayServer.LogInformation($"{address} is mapping to invoice {invoiceId}");
_WatchRequests.Add(invoiceId);
}
}
@ -99,8 +97,7 @@ namespace BTCPayServer.HostedServices
var stateBefore = invoice.Status;
var updateContext = new UpdateInvoiceContext()
{
Invoice = invoice,
KnownStates = changes
Invoice = invoice
};
await UpdateInvoice(updateContext).ConfigureAwait(false);
if (updateContext.Dirty)
@ -116,11 +113,6 @@ namespace BTCPayServer.HostedServices
_EventAggregator.Publish(evt, evt.GetType());
}
foreach (var modifiedKnownState in updateContext.ModifiedKnownStates)
{
changes.AddOrReplace(modifiedKnownState.Key, modifiedKnownState.Value);
}
if (invoice.Status == "complete" ||
((invoice.Status == "invalid" || invoice.Status == "expired") && invoice.MonitoringExpiration < DateTimeOffset.UtcNow))
{
@ -165,8 +157,6 @@ namespace BTCPayServer.HostedServices
if (coins.TimestampedCoins.Length == 0)
continue;
bool dirtyAddress = false;
if (coins.State != null)
context.ModifiedKnownStates.AddOrReplace(coins.Wallet.Network, coins.State);
var alreadyAccounted = new HashSet<OutPoint>(invoice.GetPayments(coins.Wallet.Network).Select(p => p.Outpoint));
foreach (var coin in coins.TimestampedCoins.Where(c => !alreadyAccounted.Contains(c.Coin.Outpoint)))
@ -283,7 +273,7 @@ namespace BTCPayServer.HostedServices
Strategy: d.DerivationStrategyBase))
.Where(d => d.Wallet != null)
.Select(d => (Network: d.Network,
Coins: d.Wallet.GetCoins(d.Strategy, context.KnownStates.TryGet(d.Network))))
Coins: d.Wallet.GetCoins(d.Strategy)))
.Select(async d =>
{
var coins = await d.Coins;
@ -442,7 +432,7 @@ namespace BTCPayServer.HostedServices
_Loop = StartLoop(_Cts.Token);
leases.Add(_EventAggregator.Subscribe<Events.NewBlockEvent>(async b => { await NotifyBlock(); }));
leases.Add(_EventAggregator.Subscribe<Events.TxOutReceivedEvent>(async b => { await NotifyReceived(b.ScriptPubKey, b.Network); }));
leases.Add(_EventAggregator.Subscribe<Events.TxOutReceivedEvent>(async b => { await NotifyReceived(b); }));
leases.Add(_EventAggregator.Subscribe<Events.InvoiceEvent>(async b =>
{
if (b.Name == "invoice_created")

View File

@ -25,11 +25,9 @@ namespace BTCPayServer.HostedServices
private TaskCompletionSource<bool> _RunningTask;
private CancellationTokenSource _Cts;
NBXplorerDashboard _Dashboards;
TransactionCacheProvider _TxCache;
public NBXplorerListener(ExplorerClientProvider explorerClients,
NBXplorerDashboard dashboard,
TransactionCacheProvider cacheProvider,
InvoiceRepository invoiceRepository,
EventAggregator aggregator, IApplicationLifetime lifetime)
{
@ -39,7 +37,6 @@ namespace BTCPayServer.HostedServices
_ExplorerClients = explorerClients;
_Aggregator = aggregator;
_Lifetime = lifetime;
_TxCache = cacheProvider;
}
CompositeDisposable leases = new CompositeDisposable();
@ -137,17 +134,16 @@ namespace BTCPayServer.HostedServices
switch (newEvent)
{
case NBXplorer.Models.NewBlockEvent evt:
_TxCache.GetTransactionCache(network).NewBlock(evt.Hash, evt.PreviousBlockHash);
_Aggregator.Publish(new Events.NewBlockEvent() { CryptoCode = evt.CryptoCode });
break;
case NBXplorer.Models.NewTransactionEvent evt:
foreach (var txout in evt.Outputs)
{
_TxCache.GetTransactionCache(network).AddToCache(evt.TransactionData);
_Aggregator.Publish(new Events.TxOutReceivedEvent()
{
Network = network,
ScriptPubKey = txout.ScriptPubKey
ScriptPubKey = txout.ScriptPubKey,
DerivationStrategy = txout.DerivationStrategy
});
}
break;

View File

@ -142,8 +142,6 @@ namespace BTCPayServer.Hosting
BlockTarget = 20
});
services.AddSingleton<TransactionCacheProvider>();
services.AddSingleton<IHostedService, NBXplorerWaiters>();
services.AddSingleton<IHostedService, NBXplorerListener>();
services.AddSingleton<IHostedService, InvoiceNotificationManager>();

View File

@ -1,96 +0,0 @@
using System;
using Microsoft.Extensions.Logging;
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<MemoryCacheOptions> _Options;
public TransactionCacheProvider(IOptions<MemoryCacheOptions> options)
{
_Options = options;
}
ConcurrentDictionary<string, TransactionCache> _TransactionCaches = new ConcurrentDictionary<string, TransactionCache>();
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<MemoryCacheOptions> _Options;
public TransactionCache(IOptions<MemoryCacheOptions> 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)
{
//Logging.Logs.PayServer.LogInformation($"ADD CACHE: {tx.Transaction.GetHash()} ({tx.Confirmations} conf)");
//_MemoryCache.Set(tx.Transaction.GetHash(), tx, DateTimeOffset.UtcNow + CacheSpan);
}
public TransactionResult GetTransaction(uint256 txId)
{
//var ok = _MemoryCache.TryGetValue(txId.ToString(), out object tx);
//Logging.Logs.PayServer.LogInformation($"GET CACHE: {txId} ({ok} plus {_ConfOffset})");
//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<TransactionResult>(serializer.ToString(result));
// result.Confirmations += confOffset;
// result.Height += confOffset;
//}
//return result;
return null; // Does not work correctly yet
}
public void Dispose()
{
//_MemoryCache.Dispose();
}
}
}

View File

@ -32,14 +32,16 @@ namespace BTCPayServer.Services.Wallets
public class BTCPayWallet
{
private ExplorerClient _Client;
private TransactionCache _Cache;
public BTCPayWallet(ExplorerClient client, TransactionCache cache, BTCPayNetwork network)
private IMemoryCache _MemoryCache;
public BTCPayWallet(ExplorerClient client, IMemoryCache memoryCache, BTCPayNetwork network)
{
if (client == null)
throw new ArgumentNullException(nameof(client));
if (memoryCache == null)
throw new ArgumentNullException(nameof(memoryCache));
_Client = client;
_Network = network;
_Cache = cache;
_MemoryCache = memoryCache;
}
@ -52,7 +54,7 @@ namespace BTCPayServer.Services.Wallets
}
}
public TimeSpan CacheSpan { get; private set; } = TimeSpan.FromMinutes(60);
public TimeSpan CacheSpan { get; private set; } = TimeSpan.FromMinutes(30);
public async Task<BitcoinAddress> ReserveAddressAsync(DerivationStrategyBase derivationStrategy)
{
@ -91,24 +93,29 @@ namespace BTCPayServer.Services.Wallets
{
if (txId == null)
throw new ArgumentNullException(nameof(txId));
var tx = _Cache.GetTransaction(txId);
if (tx != null)
return tx;
tx = await _Client.GetTransactionAsync(txId, cancellation);
_Cache.AddToCache(tx);
var tx = await _Client.GetTransactionAsync(txId, cancellation);
return tx;
}
public async Task<NetworkCoins> GetCoins(DerivationStrategyBase strategy, KnownState state, CancellationToken cancellation = default(CancellationToken))
public void InvalidateCache(DerivationStrategyBase strategy)
{
var changes = await _Client.GetUTXOsAsync(strategy, state?.PreviousCall, false, cancellation).ConfigureAwait(false);
return new NetworkCoins()
_MemoryCache.Remove("CACHEDCOINS_" + strategy.ToString());
}
public Task<NetworkCoins> GetCoins(DerivationStrategyBase strategy, CancellationToken cancellation = default(CancellationToken))
{
return _MemoryCache.GetOrCreateAsync("CACHEDCOINS_" + strategy.ToString(), async entry =>
{
TimestampedCoins = changes.Confirmed.UTXOs.Concat(changes.Unconfirmed.UTXOs).Select(c => new NetworkCoins.TimestampedCoin() { Coin = c.AsCoin(), DateTime = c.Timestamp }).ToArray(),
State = new KnownState() { PreviousCall = changes },
Strategy = strategy,
Wallet = this
};
entry.AbsoluteExpiration = DateTimeOffset.UtcNow + CacheSpan;
var changes = await _Client.GetUTXOsAsync(strategy, null, false, cancellation).ConfigureAwait(false);
return new NetworkCoins()
{
TimestampedCoins = changes.Confirmed.UTXOs.Concat(changes.Unconfirmed.UTXOs).Select(c => new NetworkCoins.TimestampedCoin() { Coin = c.AsCoin(), DateTime = c.Timestamp }).ToArray(),
State = new KnownState() { PreviousCall = changes },
Strategy = strategy,
Wallet = this
};
});
}
public Task<BroadcastResult[]> BroadcastTransactionsAsync(List<Transaction> transactions)
@ -117,6 +124,8 @@ namespace BTCPayServer.Services.Wallets
return Task.WhenAll(tasks);
}
public async Task<(Coin[], Dictionary<Script, KeyPath>)> GetUnspentCoins(DerivationStrategyBase derivationStrategy, CancellationToken cancellation = default(CancellationToken))
{
var changes = await _Client.GetUTXOsAsync(derivationStrategy, null, false, cancellation).ConfigureAwait(false);

View File

@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Options;
namespace BTCPayServer.Services.Wallets
{
@ -10,16 +11,16 @@ namespace BTCPayServer.Services.Wallets
{
private ExplorerClientProvider _Client;
BTCPayNetworkProvider _NetworkProvider;
TransactionCacheProvider _TransactionCacheProvider;
IOptions<MemoryCacheOptions> _Options;
public BTCPayWalletProvider(ExplorerClientProvider client,
TransactionCacheProvider transactionCacheProvider,
IOptions<MemoryCacheOptions> memoryCacheOption,
BTCPayNetworkProvider networkProvider)
{
if (client == null)
throw new ArgumentNullException(nameof(client));
_Client = client;
_TransactionCacheProvider = transactionCacheProvider;
_NetworkProvider = networkProvider;
_Options = memoryCacheOption;
}
public BTCPayWallet GetWallet(BTCPayNetwork network)
@ -36,7 +37,7 @@ namespace BTCPayServer.Services.Wallets
var client = _Client.GetExplorerClient(cryptoCode);
if (network == null || client == null)
return null;
return new BTCPayWallet(client, _TransactionCacheProvider.GetTransactionCache(network), network);
return new BTCPayWallet(client, new MemoryCache(_Options), network);
}
public bool IsAvailable(BTCPayNetwork network)

View File

@ -16,100 +16,101 @@
<div class="col-md-8">
<form method="post">
@if(!Model.Confirmation)
{
<div class="form-group">
<h5>Derivation Scheme</h5>
<span>The DerivationScheme represents the destination of the funds received by your invoice. It is generated by your wallet software. Please, verify that you are generating the right addresses by clicking on 'Check ExtPubKey'</span>
</div>
<div class="form-group">
<label asp-for="CryptoCurrency"></label>
<select asp-for="CryptoCurrency" asp-items="Model.CryptoCurrencies" class="form-control"></select>
</div>
{
<div class="form-group">
<h5>Derivation Scheme</h5>
<span>The DerivationScheme represents the destination of the funds received by your invoice. It is generated by your wallet software. Please, verify that you are generating the right addresses by clicking on 'Check ExtPubKey'</span>
</div>
<div class="form-group">
<label asp-for="CryptoCurrency"></label>
<select asp-for="CryptoCurrency" asp-items="Model.CryptoCurrencies" class="form-control"></select>
</div>
<div class="form-group">
<label asp-for="DerivationScheme"></label>
<input asp-for="DerivationScheme" class="form-control" />
<span asp-validation-for="DerivationScheme" class="text-danger"></span>
<p id="no-ledger-info" class="form-text text-muted" style="display: none;">
No ledger wallet detected. If you own one, use chrome, open the app, activate the <a href="https://support.ledgerwallet.com/hc/en-us/articles/115005198565-What-is-the-Browser-support-option-made-for-">Browser support</a>, and refresh this page.
</p>
<p id="ledger-info" class="form-text text-muted" style="display: none;">
<span>A ledger wallet is detected, please use our <a id="ledger-info-recommended" href="#">recommended choice</a></span>
</p>
</div>
<div class="form-group">
<label asp-for="DerivationSchemeFormat"></label>
<select asp-for="DerivationSchemeFormat" asp-items="Model.DerivationSchemeFormats" class="form-control"></select>
</div>
<div class="form-group">
<span>BTCPay format memo</span>
<table class="table">
<thead class="thead-inverse">
<tr>
<th>Address type</th>
<th>Example</th>
</tr>
</thead>
<tbody>
<tr>
<td>P2WPKH</td>
<td>xpub</td>
</tr>
<tr>
<td>P2SH-P2WPKH</td>
<td>xpub-[p2sh]</td>
</tr>
<tr>
<td>P2PKH</td>
<td>xpub-[legacy]</td>
</tr>
<tr>
<td>Multi-sig P2WSH</td>
<td>2-of-xpub1-xpub2</td>
</tr>
<tr>
<td>Multi-sig P2SH-P2WSH</td>
<td>2-of-xpub1-xpub2-[p2sh]</td>
</tr>
<tr>
<td>Multi-sig P2SH</td>
<td>2-of-xpub1-xpub2-[legacy]</td>
</tr>
</tbody>
</table>
</div>
<button name="command" type="submit" class="btn btn-info">Continue</button>
}
else
<div class="form-group">
<label asp-for="DerivationScheme"></label>
<input asp-for="DerivationScheme" class="form-control" />
<span asp-validation-for="DerivationScheme" class="text-danger"></span>
<p id="no-ledger-info" class="form-text text-muted" style="display: none;">
No ledger wallet detected. If you own one, use chrome, open the app, activate the <a href="https://support.ledgerwallet.com/hc/en-us/articles/115005198565-What-is-the-Browser-support-option-made-for-">Browser support</a>, and refresh this page.
</p>
<p id="ledger-info" class="form-text text-muted" style="display: none;">
<span>A ledger wallet is detected, please use our <a id="ledger-info-recommended" href="#">recommended choice</a></span>
</p>
</div>
<div class="form-group">
<label asp-for="DerivationSchemeFormat"></label>
<select asp-for="DerivationSchemeFormat" asp-items="Model.DerivationSchemeFormats" class="form-control"></select>
</div>
<div class="form-group">
<span>BTCPay format memo</span>
<table class="table">
<thead class="thead-inverse">
<tr>
<th>Address type</th>
<th>Example</th>
</tr>
</thead>
<tbody>
<tr>
<td>P2WPKH</td>
<td>xpub</td>
</tr>
<tr>
<td>P2SH-P2WPKH</td>
<td>xpub-[p2sh]</td>
</tr>
<tr>
<td>P2PKH</td>
<td>xpub-[legacy]</td>
</tr>
<tr>
<td>Multi-sig P2WSH</td>
<td>2-of-xpub1-xpub2</td>
</tr>
<tr>
<td>Multi-sig P2SH-P2WSH</td>
<td>2-of-xpub1-xpub2-[p2sh]</td>
</tr>
<tr>
<td>Multi-sig P2SH</td>
<td>2-of-xpub1-xpub2-[legacy]</td>
</tr>
</tbody>
</table>
</div>
<button name="command" type="submit" class="btn btn-info">Continue</button>
}
else
{
<div class="form-group">
<h5>Confirm the addresses (@Model.CryptoCurrency)</h5>
<span>Please check that your @Model.CryptoCurrency wallet is generating the same addresses as below.</span>
</div>
<input type="hidden" asp-for="CryptoCurrency" />
<input type="hidden" asp-for="Confirmation" />
<input type="hidden" asp-for="DerivationScheme" />
<input type="hidden" asp-for="DerivationSchemeFormat" />
<div class="form-group">
<table class="table">
<thead class="thead-inverse">
<tr>
<th>Key path</th>
<th>Address</th>
</tr>
</thead>
<tbody>
@foreach(var sample in Model.AddressSamples)
{
<div class="form-group">
<h5>Confirm the addresses (@Model.CryptoCurrency)</h5>
<span>Please check that your @Model.CryptoCurrency wallet is generating the same addresses as below.</span>
</div>
<input type="hidden" asp-for="CryptoCurrency" />
<input type="hidden" asp-for="Confirmation" />
<input type="hidden" asp-for="DerivationScheme" />
<div class="form-group">
<table class="table">
<thead class="thead-inverse">
<tr>
<th>Key path</th>
<th>Address</th>
</tr>
</thead>
<tbody>
@foreach(var sample in Model.AddressSamples)
{
<tr>
<td>@sample.KeyPath</td>
<td>@sample.Address</td>
</tr>
}
</tbody>
</table>
</div>
<button name="command" type="submit" class="btn btn-success">Confirm</button>
}
<tr>
<td>@sample.KeyPath</td>
<td>@sample.Address</td>
</tr>
}
</tbody>
</table>
</div>
<button name="command" type="submit" class="btn btn-success">Confirm</button>
}
</form>
</div>
</div>