2017-10-06 10:37:38 +09:00
using NBitcoin ;
2018-02-15 13:33:29 +09:00
using Microsoft.Extensions.Logging ;
2017-09-13 15:47:34 +09:00
using NBXplorer ;
using NBXplorer.DerivationStrategy ;
using System ;
using System.Collections.Generic ;
using System.Text ;
using System.Linq ;
using System.Threading.Tasks ;
2017-10-06 10:37:38 +09:00
using BTCPayServer.Data ;
2018-01-07 02:16:42 +09:00
using System.Threading ;
using NBXplorer.Models ;
2018-01-11 17:29:48 +09:00
using Microsoft.Extensions.Caching.Memory ;
2018-02-15 13:33:29 +09:00
using BTCPayServer.Logging ;
2018-02-15 14:44:08 +09:00
using System.Collections.Concurrent ;
2017-09-13 15:47:34 +09:00
2017-09-15 16:06:57 +09:00
namespace BTCPayServer.Services.Wallets
2017-09-13 15:47:34 +09:00
{
2018-02-17 01:34:40 +09:00
public class ReceivedCoin
{
public Coin Coin { get ; set ; }
public DateTimeOffset Timestamp { get ; set ; }
public KeyPath KeyPath { get ; set ; }
}
2018-01-10 15:43:07 +09:00
public class NetworkCoins
2018-01-07 02:16:42 +09:00
{
2018-01-10 18:30:45 +09:00
public class TimestampedCoin
{
public DateTimeOffset DateTime { get ; set ; }
public Coin Coin { get ; set ; }
}
public TimestampedCoin [ ] TimestampedCoins { get ; set ; }
2018-01-11 14:36:12 +09:00
public DerivationStrategyBase Strategy { get ; set ; }
public BTCPayWallet Wallet { get ; set ; }
2018-01-07 02:16:42 +09:00
}
2017-10-27 17:53:04 +09:00
public class BTCPayWallet
{
2018-01-11 14:36:12 +09:00
private ExplorerClient _Client ;
2018-02-15 13:02:12 +09:00
private IMemoryCache _MemoryCache ;
public BTCPayWallet ( ExplorerClient client , IMemoryCache memoryCache , BTCPayNetwork network )
2017-10-27 17:53:04 +09:00
{
if ( client = = null )
throw new ArgumentNullException ( nameof ( client ) ) ;
2018-02-15 13:02:12 +09:00
if ( memoryCache = = null )
throw new ArgumentNullException ( nameof ( memoryCache ) ) ;
2017-10-27 17:53:04 +09:00
_Client = client ;
2018-01-11 14:36:12 +09:00
_Network = network ;
2018-02-15 13:02:12 +09:00
_MemoryCache = memoryCache ;
2017-10-27 17:53:04 +09:00
}
2018-01-11 14:36:12 +09:00
private readonly BTCPayNetwork _Network ;
public BTCPayNetwork Network
2017-10-27 17:53:04 +09:00
{
2018-01-11 14:36:12 +09:00
get
{
return _Network ;
}
2017-10-27 17:53:04 +09:00
}
2018-02-17 01:34:40 +09:00
public TimeSpan CacheSpan { get ; private set ; } = TimeSpan . FromMinutes ( 5 ) ;
2018-01-11 17:29:48 +09:00
2018-01-11 14:36:12 +09:00
public async Task < BitcoinAddress > ReserveAddressAsync ( DerivationStrategyBase derivationStrategy )
2017-10-27 17:53:04 +09:00
{
2018-01-13 02:28:23 +09:00
if ( derivationStrategy = = null )
throw new ArgumentNullException ( nameof ( derivationStrategy ) ) ;
2018-01-11 14:36:12 +09:00
var pathInfo = await _Client . GetUnusedAsync ( derivationStrategy , DerivationFeature . Deposit , 0 , true ) . ConfigureAwait ( false ) ;
2018-01-13 02:28:23 +09:00
// Might happen on some broken install
if ( pathInfo = = null )
{
await _Client . TrackAsync ( derivationStrategy ) . ConfigureAwait ( false ) ;
pathInfo = await _Client . GetUnusedAsync ( derivationStrategy , DerivationFeature . Deposit , 0 , true ) . ConfigureAwait ( false ) ;
}
2018-01-12 11:54:57 +09:00
return pathInfo . ScriptPubKey . GetDestinationAddress ( Network . NBitcoinNetwork ) ;
2018-01-11 14:36:12 +09:00
}
2018-02-13 03:27:36 +09:00
public async Task < ( BitcoinAddress , KeyPath ) > GetChangeAddressAsync ( DerivationStrategyBase derivationStrategy )
{
if ( derivationStrategy = = null )
throw new ArgumentNullException ( nameof ( derivationStrategy ) ) ;
var pathInfo = await _Client . GetUnusedAsync ( derivationStrategy , DerivationFeature . Change , 0 , false ) . ConfigureAwait ( false ) ;
// Might happen on some broken install
if ( pathInfo = = null )
{
await _Client . TrackAsync ( derivationStrategy ) . ConfigureAwait ( false ) ;
pathInfo = await _Client . GetUnusedAsync ( derivationStrategy , DerivationFeature . Change , 0 , false ) . ConfigureAwait ( false ) ;
}
return ( pathInfo . ScriptPubKey . GetDestinationAddress ( Network . NBitcoinNetwork ) , pathInfo . KeyPath ) ;
}
2018-01-11 14:36:12 +09:00
public async Task TrackAsync ( DerivationStrategyBase derivationStrategy )
{
await _Client . TrackAsync ( derivationStrategy ) ;
2017-10-27 17:53:04 +09:00
}
2018-01-11 21:01:00 +09:00
public async Task < TransactionResult > GetTransactionAsync ( uint256 txId , CancellationToken cancellation = default ( CancellationToken ) )
2018-01-07 02:16:42 +09:00
{
2018-01-10 18:30:45 +09:00
if ( txId = = null )
throw new ArgumentNullException ( nameof ( txId ) ) ;
2018-02-15 12:42:48 +09:00
var tx = await _Client . GetTransactionAsync ( txId , cancellation ) ;
2018-01-11 21:01:00 +09:00
return tx ;
2018-01-07 02:16:42 +09:00
}
2018-02-15 13:02:12 +09:00
public void InvalidateCache ( DerivationStrategyBase strategy )
2018-01-07 02:16:42 +09:00
{
2018-02-15 13:02:12 +09:00
_MemoryCache . Remove ( "CACHEDCOINS_" + strategy . ToString ( ) ) ;
2018-02-15 15:17:12 +09:00
_FetchingUTXOs . TryRemove ( strategy . ToString ( ) , out var unused ) ;
2018-02-15 13:02:12 +09:00
}
2018-02-15 14:44:08 +09:00
ConcurrentDictionary < string , TaskCompletionSource < UTXOChanges > > _FetchingUTXOs = new ConcurrentDictionary < string , TaskCompletionSource < UTXOChanges > > ( ) ;
2018-02-15 13:33:29 +09:00
private async Task < UTXOChanges > GetUTXOChanges ( DerivationStrategyBase strategy , CancellationToken cancellation )
{
2018-02-15 14:44:08 +09:00
var thisCompletionSource = new TaskCompletionSource < UTXOChanges > ( ) ;
var completionSource = _FetchingUTXOs . GetOrAdd ( strategy . ToString ( ) , ( s ) = > thisCompletionSource ) ;
if ( thisCompletionSource ! = completionSource )
return await completionSource . Task ;
try
2018-02-15 13:33:29 +09:00
{
2018-02-15 14:44:08 +09:00
var utxos = await _MemoryCache . GetOrCreateAsync ( "CACHEDCOINS_" + strategy . ToString ( ) , async entry = >
2018-02-15 13:02:12 +09:00
{
2018-02-15 14:44:08 +09:00
var now = DateTimeOffset . UtcNow ;
UTXOChanges result = null ;
try
{
result = await _Client . GetUTXOsAsync ( strategy , null , false , cancellation ) . ConfigureAwait ( false ) ;
}
catch
{
2018-03-17 19:26:30 +09:00
Logs . PayServer . LogError ( $"{Network.CryptoCode}: Call to NBXplorer GetUTXOsAsync timed out, this should never happen, please report this issue to NBXplorer developers" ) ;
2018-02-15 14:44:08 +09:00
throw ;
}
var spentTime = DateTimeOffset . UtcNow - now ;
if ( spentTime . TotalSeconds > 30 )
{
2018-03-17 19:26:30 +09:00
Logs . PayServer . LogWarning ( $"{Network.CryptoCode}: NBXplorer took {(int)spentTime.TotalSeconds} seconds to reply, there is something wrong, please report this issue to NBXplorer developers" ) ;
2018-02-15 14:44:08 +09:00
}
entry . AbsoluteExpiration = DateTimeOffset . UtcNow + CacheSpan ;
return result ;
} ) ;
2018-03-13 15:39:52 +09:00
_FetchingUTXOs . TryRemove ( strategy . ToString ( ) , out var unused ) ;
2018-02-15 14:44:08 +09:00
completionSource . TrySetResult ( utxos ) ;
}
2018-02-17 01:34:40 +09:00
catch ( Exception ex )
2018-02-15 14:44:08 +09:00
{
completionSource . TrySetException ( ex ) ;
}
finally
{
_FetchingUTXOs . TryRemove ( strategy . ToString ( ) , out var unused ) ;
}
return await completionSource . Task ;
2018-01-07 02:16:42 +09:00
}
2018-02-13 03:27:36 +09:00
public Task < BroadcastResult [ ] > BroadcastTransactionsAsync ( List < Transaction > transactions )
2017-10-27 17:53:04 +09:00
{
2018-01-11 14:36:12 +09:00
var tasks = transactions . Select ( t = > _Client . BroadcastAsync ( t ) ) . ToArray ( ) ;
2017-10-27 17:53:04 +09:00
return Task . WhenAll ( tasks ) ;
}
2018-02-15 13:02:12 +09:00
2018-02-17 01:34:40 +09:00
public async Task < ReceivedCoin [ ] > GetUnspentCoins ( DerivationStrategyBase derivationStrategy , CancellationToken cancellation = default ( CancellationToken ) )
2018-02-13 03:27:36 +09:00
{
2018-02-17 01:34:40 +09:00
if ( derivationStrategy = = null )
throw new ArgumentNullException ( nameof ( derivationStrategy ) ) ;
return ( await GetUTXOChanges ( derivationStrategy , cancellation ) )
. GetUnspentUTXOs ( )
. Select ( c = > new ReceivedCoin ( )
{
Coin = c . AsCoin ( derivationStrategy ) ,
KeyPath = c . KeyPath ,
Timestamp = c . Timestamp
} ) . ToArray ( ) ;
2018-02-13 03:27:36 +09:00
}
2018-01-10 15:43:07 +09:00
2018-02-15 13:33:29 +09:00
public async Task < Money > GetBalance ( DerivationStrategyBase derivationStrategy , CancellationToken cancellation = default ( CancellationToken ) )
2017-10-27 17:53:04 +09:00
{
2018-02-15 13:33:29 +09:00
UTXOChanges changes = await GetUTXOChanges ( derivationStrategy , cancellation ) ;
return changes . GetUnspentUTXOs ( ) . Select ( c = > c . Value ) . Sum ( ) ;
2017-10-27 17:53:04 +09:00
}
}
2017-09-13 15:47:34 +09:00
}