diff --git a/BTCPayServerPlugins.sln b/BTCPayServerPlugins.sln index 1978044..611de98 100644 --- a/BTCPayServerPlugins.sln +++ b/BTCPayServerPlugins.sln @@ -27,8 +27,6 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BTCPayServer.Plugins.FixedF EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BTCPayServer.Plugins.LiquidPlus", "Plugins\BTCPayServer.Plugins.LiquidPlus\BTCPayServer.Plugins.LiquidPlus.csproj", "{B4E2ED08-4AD3-4648-8BDB-3107200460B9}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BTCPayServer.Plugins.NFC", "Plugins\BTCPayServer.Plugins.NFC\BTCPayServer.Plugins.NFC.csproj", "{71885A5E-1B00-4676-9566-D81AAE37406C}" -EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BTCPayServer.Plugins.SideShift", "Plugins\BTCPayServer.Plugins.SideShift\BTCPayServer.Plugins.SideShift.csproj", "{5E1BAA06-7828-47BC-89D6-19C2A78EA427}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BTCPayServer.Plugins.TicketTailor", "Plugins\BTCPayServer.Plugins.TicketTailor\BTCPayServer.Plugins.TicketTailor.csproj", "{7AFC20EB-1696-47D7-8E57-822B05DD18F2}" @@ -151,14 +149,6 @@ Global {B4E2ED08-4AD3-4648-8BDB-3107200460B9}.Altcoins-Debug|Any CPU.Build.0 = Altcoins-Debug|Any CPU {B4E2ED08-4AD3-4648-8BDB-3107200460B9}.Altcoins-Release|Any CPU.ActiveCfg = Altcoins-Release|Any CPU {B4E2ED08-4AD3-4648-8BDB-3107200460B9}.Altcoins-Release|Any CPU.Build.0 = Altcoins-Release|Any CPU - {71885A5E-1B00-4676-9566-D81AAE37406C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {71885A5E-1B00-4676-9566-D81AAE37406C}.Debug|Any CPU.Build.0 = Debug|Any CPU - {71885A5E-1B00-4676-9566-D81AAE37406C}.Release|Any CPU.ActiveCfg = Release|Any CPU - {71885A5E-1B00-4676-9566-D81AAE37406C}.Release|Any CPU.Build.0 = Release|Any CPU - {71885A5E-1B00-4676-9566-D81AAE37406C}.Altcoins-Debug|Any CPU.ActiveCfg = Debug|Any CPU - {71885A5E-1B00-4676-9566-D81AAE37406C}.Altcoins-Debug|Any CPU.Build.0 = Debug|Any CPU - {71885A5E-1B00-4676-9566-D81AAE37406C}.Altcoins-Release|Any CPU.ActiveCfg = Release|Any CPU - {71885A5E-1B00-4676-9566-D81AAE37406C}.Altcoins-Release|Any CPU.Build.0 = Release|Any CPU {5E1BAA06-7828-47BC-89D6-19C2A78EA427}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {5E1BAA06-7828-47BC-89D6-19C2A78EA427}.Debug|Any CPU.Build.0 = Debug|Any CPU {5E1BAA06-7828-47BC-89D6-19C2A78EA427}.Release|Any CPU.ActiveCfg = Release|Any CPU diff --git a/Plugins/BTCPayServer.Plugins.Wabisabi/BTCPayServer.Plugins.Wabisabi.csproj b/Plugins/BTCPayServer.Plugins.Wabisabi/BTCPayServer.Plugins.Wabisabi.csproj index b2ce431..79a5ba3 100644 --- a/Plugins/BTCPayServer.Plugins.Wabisabi/BTCPayServer.Plugins.Wabisabi.csproj +++ b/Plugins/BTCPayServer.Plugins.Wabisabi/BTCPayServer.Plugins.Wabisabi.csproj @@ -2,7 +2,7 @@ net6.0 - 10 + 11 Debug;Release AnyCPU @@ -13,7 +13,7 @@ Wabisabi Coinjoin Allows you to integrate your btcpayserver store with coinjoins. - 1.0.55 + 1.0.56 @@ -43,7 +43,7 @@ - + diff --git a/Plugins/BTCPayServer.Plugins.Wabisabi/BTCPayWallet.cs b/Plugins/BTCPayServer.Plugins.Wabisabi/BTCPayWallet.cs index 430e027..faa8857 100644 --- a/Plugins/BTCPayServer.Plugins.Wabisabi/BTCPayWallet.cs +++ b/Plugins/BTCPayServer.Plugins.Wabisabi/BTCPayWallet.cs @@ -15,6 +15,7 @@ using BTCPayServer.Payments.PayJoin; using BTCPayServer.Services; using BTCPayServer.Services.Stores; using BTCPayServer.Services.Wallets; +using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.Logging; using NBitcoin; using NBXplorer; @@ -53,7 +54,8 @@ public class BTCPayWallet : IWallet, IDestinationProvider public readonly ILogger Logger; public static readonly BlockchainAnalyzer BlockchainAnalyzer = new(); - public BTCPayWallet(WalletRepository walletRepository, + public BTCPayWallet( + WalletRepository walletRepository, BTCPayNetworkProvider btcPayNetworkProvider, BitcoinLikePayoutHandler bitcoinLikePayoutHandler, BTCPayNetworkJsonSerializerSettings btcPayNetworkJsonSerializerSettings, @@ -66,7 +68,8 @@ public class BTCPayWallet : IWallet, IDestinationProvider WabisabiStoreSettings wabisabiStoreSettings, IUTXOLocker utxoLocker, ILoggerFactory loggerFactory, - StoreRepository storeRepository) + StoreRepository storeRepository, + IMemoryCache memoryCache) { KeyChain = keyChain; _walletRepository = walletRepository; @@ -81,6 +84,7 @@ public class BTCPayWallet : IWallet, IDestinationProvider WabisabiStoreSettings = wabisabiStoreSettings; UtxoLocker = utxoLocker; _storeRepository = storeRepository; + _memoryCache = memoryCache; Logger = loggerFactory.CreateLogger($"BTCPayWallet_{storeId}"); } @@ -147,6 +151,7 @@ public class BTCPayWallet : IWallet, IDestinationProvider private IRoundCoinSelector _coinSelector; public Smartifier _smartifier => (KeyChain as BTCPayKeyChain)?.Smartifier; private readonly StoreRepository _storeRepository; + private readonly IMemoryCache _memoryCache; public IRoundCoinSelector GetCoinSelector() { @@ -492,7 +497,7 @@ public class BTCPayWallet : IWallet, IDestinationProvider }))}, "utxo"); } - _smartifier.Transactions.AddOrReplace(txHash, Task.FromResult(smartTx)); + _smartifier.SmartTransactions.AddOrReplace(txHash, Task.FromResult(smartTx)); // // var kp = await ExplorerClient.GetMetadataAsync(DerivationScheme, // WellknownMetadataKeys.AccountKeyPath); @@ -552,6 +557,7 @@ public class BTCPayWallet : IWallet, IDestinationProvider stopwatch.Stop(); Logger.LogInformation($"Registered coinjoin result for {StoreId} in {stopwatch.Elapsed}"); + _memoryCache.Remove(WabisabiService.GetCacheKey(StoreId) + "cjhistory"); } catch (Exception e) diff --git a/Plugins/BTCPayServer.Plugins.Wabisabi/Coordinator/WabisabiCoordinatorConfigController.cs b/Plugins/BTCPayServer.Plugins.Wabisabi/Coordinator/WabisabiCoordinatorConfigController.cs index 9466683..a55012c 100644 --- a/Plugins/BTCPayServer.Plugins.Wabisabi/Coordinator/WabisabiCoordinatorConfigController.cs +++ b/Plugins/BTCPayServer.Plugins.Wabisabi/Coordinator/WabisabiCoordinatorConfigController.cs @@ -97,7 +97,7 @@ Reputation risks: as the coordinator, the user may be associated with illegal ac else { vm.UriToAdvertise = Request.GetAbsoluteRootUri(); - TempData["SuccessMessage"] = $"Will create nostr events that point to ${ vm.UriToAdvertise }"; + TempData["SuccessMessage"] = $"Will create nostr events that point to { vm.UriToAdvertise }"; await _wabisabiCoordinatorService.UpdateSettings( vm); return RedirectToAction(nameof(UpdateWabisabiSettings)); } diff --git a/Plugins/BTCPayServer.Plugins.Wabisabi/Coordinator/WabisabiCoordinatorService.cs b/Plugins/BTCPayServer.Plugins.Wabisabi/Coordinator/WabisabiCoordinatorService.cs index 23ba7e4..23ca820 100644 --- a/Plugins/BTCPayServer.Plugins.Wabisabi/Coordinator/WabisabiCoordinatorService.cs +++ b/Plugins/BTCPayServer.Plugins.Wabisabi/Coordinator/WabisabiCoordinatorService.cs @@ -195,9 +195,7 @@ public class WabisabiCoordinatorService : PeriodicRunner var coordinatorParameters = new CoordinatorParameters(Path.Combine(_dataDirectories.Value.DataDir, "Plugins", "Coinjoin")); var coinJoinIdStore = - CoinJoinIdStore.Create( - Path.Combine(coordinatorParameters.ApplicationDataDir, "CcjCoordinator", - $"CoinJoins{explorerClient.Network}.txt"), coordinatorParameters.CoinJoinIdStoreFilePath); + CoinJoinIdStore.Create( coordinatorParameters.CoinJoinIdStoreFilePath); var coinJoinScriptStore = CoinJoinScriptStore.LoadFromFile(coordinatorParameters.CoinJoinScriptStoreFilePath); var rpc = new BtcPayRpcClient(explorerClient.RPCClient, _memoryCache, explorerClient); diff --git a/Plugins/BTCPayServer.Plugins.Wabisabi/Extensions.cs b/Plugins/BTCPayServer.Plugins.Wabisabi/Extensions.cs index cc9fb9b..7860d4f 100644 --- a/Plugins/BTCPayServer.Plugins.Wabisabi/Extensions.cs +++ b/Plugins/BTCPayServer.Plugins.Wabisabi/Extensions.cs @@ -1,12 +1,18 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; +using System.Text.RegularExpressions; using System.Threading.Tasks; namespace BTCPayServer.Plugins.Wabisabi; public static class Extensions { + public static string ToSentenceCase(this string str) + { + return Regex.Replace(str, "[a-z][A-Z]", m => m.Value[0] + " " + char.ToLower(m.Value[1])); + } + /// /// Returns an existing task from the concurrent dictionary, or adds a new task /// using the specified asynchronous factory method. Concurrent invocations for diff --git a/Plugins/BTCPayServer.Plugins.Wabisabi/Smartifier.cs b/Plugins/BTCPayServer.Plugins.Wabisabi/Smartifier.cs index 9d7feba..50fd168 100644 --- a/Plugins/BTCPayServer.Plugins.Wabisabi/Smartifier.cs +++ b/Plugins/BTCPayServer.Plugins.Wabisabi/Smartifier.cs @@ -8,6 +8,7 @@ using BTCPayServer.Abstractions.Contracts; using BTCPayServer.Payments.PayJoin; using BTCPayServer.Services; using BTCPayServer.Services.Wallets; +using Microsoft.Extensions.Logging; using NBitcoin; using NBXplorer; using NBXplorer.DerivationStrategy; @@ -22,6 +23,7 @@ namespace BTCPayServer.Plugins.Wabisabi; public class Smartifier { + private readonly ILogger _logger; private readonly WalletRepository _walletRepository; private readonly ExplorerClient _explorerClient; public DerivationStrategyBase DerivationScheme { get; } @@ -29,10 +31,12 @@ public class Smartifier private readonly IUTXOLocker _utxoLocker; public Smartifier( + ILogger logger, WalletRepository walletRepository, ExplorerClient explorerClient, DerivationStrategyBase derivationStrategyBase, string storeId, IUTXOLocker utxoLocker, RootedKeyPath accountKeyPath) { + _logger = logger; _walletRepository = walletRepository; _explorerClient = explorerClient; DerivationScheme = derivationStrategyBase; @@ -40,10 +44,49 @@ public class Smartifier _utxoLocker = utxoLocker; _accountKeyPath = accountKeyPath; } - - public readonly ConcurrentDictionary> CachedTransactions = new(); - public readonly ConcurrentDictionary> Transactions = new(); + public readonly ConcurrentDictionary>> TransactionInformations = new(); + public readonly ConcurrentDictionary> SmartTransactions = new(); public readonly ConcurrentDictionary> Coins = new(); + + public static async Task GetOrCreate(ConcurrentDictionary>> collection, Y key, Func> create, ILogger logger = null) + { + var lazyTask = new Lazy>(() => FetchFromServer(create, logger, key)); + + // Even if multiple threads provide their own new Lazy instances, only one will be stored. + var task = collection.GetOrAdd(key, lazyTask).Value; + + try + { + return await task; + } + catch (Exception) + { + // If there's an error, remove the lazy task from the dictionary. + collection.TryRemove(key, out _); + // The error has already been logged inside FetchFromServer. + return default; + } + } + + private static async Task FetchFromServer(Func> create, ILogger logger, Y key) + { + try + { + return await create(); + } + catch (Exception e) + { + logger?.LogError(e, "Error while loading(and caching) {key}", key); + throw; // Re-throw the exception so the outer catch can handle it. + } + } + + + public async Task GetTransactionInfo(uint256 hash) + { + return await GetOrCreate(TransactionInformations , hash, () => _explorerClient.GetTransactionAsync(DerivationScheme, hash), _logger); + } + private readonly RootedKeyPath _accountKeyPath; public async Task LoadCoins(List coins, int current , @@ -57,15 +100,14 @@ public class Smartifier var txs = coins.Select(data => data.OutPoint.Hash).Distinct(); foreach (uint256 tx in txs) { - if(!CachedTransactions.ContainsKey(tx)) - CachedTransactions.TryAdd(tx, _explorerClient.GetTransactionAsync(DerivationScheme, tx)); + _ =GetTransactionInfo(tx); } foreach (var coin in coins) { - var tx = await Transactions.GetOrAdd(coin.OutPoint.Hash, async uint256 => + var tx = await SmartTransactions.GetOrAdd(coin.OutPoint.Hash, async uint256 => { - var unsmartTx = await CachedTransactions[coin.OutPoint.Hash]; + var unsmartTx = await GetTransactionInfo(coin.OutPoint.Hash); if (unsmartTx?.Transaction is null) { return null; @@ -85,17 +127,7 @@ public class Smartifier potentialMatches.TryAdd(matchedInput, potentialMatchesForInput.ToArray()); foreach (IndexedTxIn potentialMatchForInput in potentialMatchesForInput) { - TransactionInformation ti = null; - try - { - ti = await CachedTransactions.GetOrAdd(potentialMatchForInput.PrevOut.Hash, - _explorerClient.GetTransactionAsync(DerivationScheme, - potentialMatchForInput.PrevOut.Hash)); - } - catch (Exception e) - { - CachedTransactions.Remove(potentialMatchForInput.PrevOut.Hash, out _); - } + var ti = await GetTransactionInfo(potentialMatchForInput.PrevOut.Hash); if (ti is not null) { MatchedOutput found = ti.Outputs.Find(output => @@ -157,7 +189,6 @@ public class Smartifier var smartCoin = await Coins.GetOrAdd(coin.OutPoint, async point => { utxoLabels.TryGetValue(coin.OutPoint, out var labels); - var unsmartTx = await CachedTransactions[coin.OutPoint.Hash]; var pubKey = DerivationScheme.GetChild(coin.KeyPath).GetExtPubKeys().First().PubKey; //if there is no account key path, it most likely means this is a watch only wallet. Fake the key path var kp = _accountKeyPath?.Derive(coin.KeyPath).KeyPath ?? new KeyPath(0,0,0,0,0); diff --git a/Plugins/BTCPayServer.Plugins.Wabisabi/Views/Shared/Wabisabi/AddCoordinator.cshtml b/Plugins/BTCPayServer.Plugins.Wabisabi/Views/Shared/Wabisabi/AddCoordinator.cshtml index 90d5119..3270055 100644 --- a/Plugins/BTCPayServer.Plugins.Wabisabi/Views/Shared/Wabisabi/AddCoordinator.cshtml +++ b/Plugins/BTCPayServer.Plugins.Wabisabi/Views/Shared/Wabisabi/AddCoordinator.cshtml @@ -3,7 +3,7 @@ @using BTCPayServer.Abstractions.Contracts @model WalletWasabi.Backend.Controllers.DiscoveredCoordinator @inject IScopeProvider ScopeProvider -
+ @@ -19,7 +19,7 @@

@Model.Description

- +
diff --git a/Plugins/BTCPayServer.Plugins.Wabisabi/Views/Shared/Wabisabi/AddCoordinatorPrompt.cshtml b/Plugins/BTCPayServer.Plugins.Wabisabi/Views/Shared/Wabisabi/AddCoordinatorPrompt.cshtml index 09c4ca6..bae41fb 100644 --- a/Plugins/BTCPayServer.Plugins.Wabisabi/Views/Shared/Wabisabi/AddCoordinatorPrompt.cshtml +++ b/Plugins/BTCPayServer.Plugins.Wabisabi/Views/Shared/Wabisabi/AddCoordinatorPrompt.cshtml @@ -10,10 +10,10 @@ - +@* *@