wabiupdate

This commit is contained in:
Kukks
2023-09-28 16:11:03 +02:00
parent 61c2817435
commit c738a1b053
19 changed files with 372 additions and 175 deletions

View File

@@ -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

View File

@@ -2,7 +2,7 @@
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<LangVersion>10</LangVersion>
<LangVersion>11</LangVersion>
<Configurations>Debug;Release</Configurations>
<Platforms>AnyCPU</Platforms>
</PropertyGroup>
@@ -13,7 +13,7 @@
<PropertyGroup>
<Product>Wabisabi Coinjoin</Product>
<Description>Allows you to integrate your btcpayserver store with coinjoins.</Description>
<Version>1.0.55</Version>
<Version>1.0.56</Version>
</PropertyGroup>
<!-- Plugin development properties -->
@@ -43,7 +43,7 @@
</ProjectReference>
</ItemGroup>
<ItemGroup>
<PackageReference Include="NNostr.Client" Version="0.0.34" />
<PackageReference Include="NNostr.Client" Version="0.0.37" />
<PackageReference Include="WabiSabi" Version="1.0.1.2" />
</ItemGroup>
<Target Name="DeleteExampleFile" AfterTargets="Publish">

View File

@@ -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<RootedKeyPath>(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)

View File

@@ -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));
}

View File

@@ -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);

View File

@@ -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]));
}
/// <summary>
/// Returns an existing task from the concurrent dictionary, or adds a new task
/// using the specified asynchronous factory method. Concurrent invocations for

View File

@@ -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<uint256, Task<TransactionInformation>> CachedTransactions = new();
public readonly ConcurrentDictionary<uint256, Task<SmartTransaction>> Transactions = new();
public readonly ConcurrentDictionary<uint256, Lazy<Task<TransactionInformation>>> TransactionInformations = new();
public readonly ConcurrentDictionary<uint256, Task<SmartTransaction>> SmartTransactions = new();
public readonly ConcurrentDictionary<OutPoint, Task<SmartCoin>> Coins = new();
public static async Task<T?> GetOrCreate<T, Y>(ConcurrentDictionary<Y, Lazy<Task<T?>>> collection, Y key, Func<Task<T?>> create, ILogger logger = null)
{
var lazyTask = new Lazy<Task<T?>>(() => 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<T?> FetchFromServer<T, Y>(Func<Task<T?>> 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<TransactionInformation?> GetTransactionInfo(uint256 hash)
{
return await GetOrCreate(TransactionInformations , hash, () => _explorerClient.GetTransactionAsync(DerivationScheme, hash), _logger);
}
private readonly RootedKeyPath _accountKeyPath;
public async Task LoadCoins(List<ReceivedCoin> 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);

View File

@@ -3,7 +3,7 @@
@using BTCPayServer.Abstractions.Contracts
@model WalletWasabi.Backend.Controllers.DiscoveredCoordinator
@inject IScopeProvider ScopeProvider
<form asp-action="AddCoordinator" method="post" class="card mt-3" asp-route-storeId="@ScopeProvider.GetCurrentStoreId()" permission="@Policies.CanModifyServerSettings">
<form asp-action="AddCoordinator" asp-controller="WabisabiStore" method="post" class="card mt-3" asp-route-storeId="@ScopeProvider.GetCurrentStoreId()" permission="@Policies.CanModifyServerSettings">
<input type="hidden" asp-for="Description"/>
<input type="hidden" asp-for="Name"/>
<input type="hidden" asp-for="Uri"/>
@@ -19,7 +19,7 @@
<p>@Model.Description</p>
</div>
<div class="form-group form-check">
<button name="command" type="submit" class="btn btn-primary btn-lg">Add</button>
<button name="command" type="submit" class="btn btn-primary btn-lg">Add</button>
</div>
</div>

View File

@@ -10,10 +10,10 @@
<button type="button" class="btn btn-secondary mt-2" permission="@Policies.CanModifyServerSettings"
data-bs-toggle="modal" data-bs-target="#discover-prompt">
Add Coordinator
</button>
@* <button type="button" class="btn btn-secondary mt-2" permission="@Policies.CanModifyServerSettings" *@
@* data-bs-toggle="modal" data-bs-target="#discover-prompt"> *@
@* Add Coordinator *@
@* </button> *@
<div class="modal fade" id="discover-prompt" permission="@Policies.CanModifyServerSettings">
<div class="modal-dialog">
<div class="modal-content">
@@ -30,7 +30,7 @@
</li>
</ul>
<div class="tab-content ">
<form asp-action="AddCoordinator" asp-route-storeId="@ScopeProvider.GetCurrentStoreId()"
<form asp-action="AddCoordinator" asp-controller="WabisabiStore" asp-route-storeId="@ScopeProvider.GetCurrentStoreId()"
class="tab-pane fade show active " id="nostr-tab-pane" role="tabpanel" aria-labelledby="home-tab" tabindex="0">
<div class="modal-body">
@@ -45,7 +45,7 @@
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
</div>
</form>
<form asp-action="AddCoordinator" asp-route-storeId="@ScopeProvider.GetCurrentStoreId()"
<form asp-action="AddCoordinator" asp-controller="WabisabiStore" asp-route-storeId="@ScopeProvider.GetCurrentStoreId()"
class="tab-pane fade" id="manual-tab-pane" role="tabpanel" tabindex="0">
<div class="modal-body">

View File

@@ -1,15 +1,13 @@
@using BTCPayServer.Abstractions.Contracts
@using BTCPayServer.Client
@using BTCPayServer.Client.Models
@using BTCPayServer.Common
@using BTCPayServer.Plugins.Wabisabi
@using BTCPayServer.Security
@using Microsoft.AspNetCore.Mvc.TagHelpers
@using NBitcoin
@using WalletWasabi.Blockchain.Analysis
@using WalletWasabi.Extensions
@using WalletWasabi.WabiSabi.Backend.Rounds
@using WalletWasabi.WabiSabi.Client
@using WalletWasabi.WabiSabi.Models
@model object
@inject IScopeProvider ScopeProvider
@inject BTCPayServerClient Client
@inject WabisabiService WabisabiService;
@inject WalletProvider WalletProvider;
@inject WabisabiCoordinatorClientInstanceManager WabisabiCoordinatorClientInstanceManager
@@ -22,12 +20,6 @@
{
return;
}
@if (!(await ExplorerClientProvider.GetExplorerClient("BTC").GetStatusAsync()).IsFullySynched)
{
available = false;
return;
}
var storeId = ScopeProvider.GetCurrentStoreId();
}
@@ -57,7 +49,7 @@
}
else
{
<div class="widget store-wallet-balance" >
<div class="widget store-wallet-balance">
<header>
<h3>Recent Coinjoins</h3>
@if (cjHistory.Any())
@@ -89,7 +81,7 @@
var privacyPercentage = Math.Round(privacy * 100);
var colorCoins = coins.GroupBy(coin => coin.CoinColor(wallet.AnonScoreTarget)).ToDictionary(grouping => grouping.Key, grouping => grouping);
<div class="widget store-numbers" >
<div class="widget store-numbers">
@if (wallet is { })
{
@@ -151,35 +143,35 @@
}
</div>
</div>
@{
var coinjoined = @coins.CoinJoinInProcess();
}
@if (coinjoined.Any())
{
var count = @coins.CoinJoinInProcess().Count();
var totalCount = @coins.Count();
var sum = @coinjoined.TotalAmount().ToDecimal(MoneyUnit.BTC);
var totalSum = @coins.TotalAmount().ToDecimal(MoneyUnit.BTC);
var sumPercentage = decimal.Divide(sum, totalSum) * 100;
var countPercentage = decimal.Divide(count, totalCount) * 100;
<div>
<h6 class="mb-2">Coins currently joining</h6>
<div class="progress mb-2 position-relative" style="height: 2rem;">
<div class="w-100 text-center position-absolute bg-transparent progress-bar h-100">@count </div>
<div class="progress-bar bg-info progress-bar-striped progress-bar-animated w-100" role="progressbar"></div>
</div>
</div>
<div>
<h6 class="mb-2">Value currently joining</h6>
<div class="progress mb-2 position-relative" style="height: 2rem;">
<div class="w-100 text-center position-absolute bg-transparent progress-bar h-100">@sum BTC</div>
<div class="progress-bar bg-info progress-bar-striped progress-bar-animated w-100" role="progressbar"></div>
</div>
</div>
}
@* @{ *@
@* var coinjoined = @coins.CoinJoinInProcess(); *@
@* } *@
@* @if (coinjoined.Any()) *@
@* { *@
@* var count = @coins.CoinJoinInProcess().Count(); *@
@* var totalCount = @coins.Count(); *@
@* var sum = @coinjoined.TotalAmount().ToDecimal(MoneyUnit.BTC); *@
@* var totalSum = @coins.TotalAmount().ToDecimal(MoneyUnit.BTC); *@
@* var sumPercentage = decimal.Divide(sum, totalSum) * 100; *@
@* var countPercentage = decimal.Divide(count, totalCount) * 100; *@
@* *@
@* <div> *@
@* <h6 class="mb-2">Coins currently joining</h6> *@
@* <div class="progress mb-2 position-relative" style="height: 2rem;"> *@
@* <div class="w-100 text-center position-absolute bg-transparent progress-bar h-100">@count </div> *@
@* <div class="progress-bar bg-info progress-bar-striped progress-bar-animated w-100" role="progressbar"></div> *@
@* </div> *@
@* </div> *@
@* <div> *@
@* <h6 class="mb-2">Value currently joining</h6> *@
@* <div class="progress mb-2 position-relative" style="height: 2rem;"> *@
@* <div class="w-100 text-center position-absolute bg-transparent progress-bar h-100">@sum BTC</div> *@
@* <div class="progress-bar bg-info progress-bar-striped progress-bar-animated w-100" role="progressbar"></div> *@
@* *@
@* *@
@* </div> *@
@* </div> *@
@* } *@
<!-- Modal -->
@@ -272,7 +264,7 @@
</div>
</div>
</div>
<div class="list-group list-group-flush mb-2">
<div class="list-group list-group-flush mt-4 mb-3">
<h5 class="list-group-item-heading text-muted">Enabled coordinators</h5>
@{
@@ -282,42 +274,172 @@
{
continue;
}
<div class="list-group-item">
<h6>@coordinator.CoordinatorDisplayName</h6>
<div class="row ">
<span class="text-muted col-sm-12 col-xxl-9 p-0 text-break">
@coordinator.Coordinator
</span>
@if (!coordinator.WasabiCoordinatorStatusFetcher.Connected)
RoundState currentRound = null;
CoinJoinTracker tracker = null;
if (coordinator.CoinJoinManager.TrackedCoinJoins?.TryGetValue(wallet.WalletName, out tracker) is true &&
tracker?.CoinJoinClient?.CurrentRoundId is { } &&
tracker?.CoinJoinClient?.RoundStatusUpdater?.RoundStates?.TryGetValue(tracker?.CoinJoinClient?.CurrentRoundId, out currentRound) is true)
{
}
var statusMsg = coordinator.WasabiCoordinatorStatusFetcher.Connected ? $"Connected to {(coordinator.Coordinator?.ToString() ?? "local")}" : $"Not connected to {(coordinator.Coordinator?.ToString() ?? "local")}";
<div class="list-group-item">
<div class="d-flex flex-wrap align-items-center justify-content-between gap-3">
<div class="d-flex flex-wrap align-items-center justify-content-between gap-3" data-bs-toggle="tooltip" title="@statusMsg">
<span class="btcpay-status btcpay-status--@(coordinator.WasabiCoordinatorStatusFetcher.Connected ? "enabled" : "disabled")"></span>
<h6>@coordinator.CoordinatorDisplayName</h6>
</div>
@if(currentRound is not null)
{
<p class="text-danger mb-0 col-sm-12 col-xxl-3 p-0 text-break">Not connected</p>
}
else
<div class="timer cursor-pointer" data-bs-toggle="collapse" data-bs-target="#cj-@currentRound.Id">
<span class="spinner-border spinner-border-sm" role="status">
<span class="visually-hidden"></span>
</span>
<span class="h6">Mixing</span>
<vc:icon symbol="caret-down" />
</div>
}else if( coordinator.WasabiCoordinatorStatusFetcher.Connected)
{
<p class="text-success mb-0 col-sm-12 col-xxl-3 p-0 text-break">Connected</p>
<span class="h6">Idle</span>
}
</div>
@{
if (coordinator.CoinPrison is not null)
{
var bannedCoins = coins.Where(coin => coordinator.CoinPrison.TryGetOrRemoveBannedCoin(coin.Outpoint, out _));
var bannedCoins = coins.Where(coin => coordinator.CoinPrison.TryGetOrRemoveBannedCoin(coin.Outpoint, out _));
@if (bannedCoins.Any())
{
<div>
<h6 class="mb-2">Coins currently banned (for disrupting rounds)</h6>
<div class="progress mb-2 position-relative" style="height: 2rem;">
<div class="w-100 text-center position-absolute bg-transparent progress-bar h-100">@bannedCoins.Count() </div>
<div class="progress-bar bg-danger w-100" role="progressbar"></div>
</div>
</div>
<div class="text-muted">@bannedCoins.Count() banned coins(for disrupting rounds)</div>
}
}
if (currentRound is not null)
{
<div class="collapse table-responsive @(enabledSettings.Count() ==1? "show": "")" id="cj-@currentRound.Id">
<table class="table ">
<tr>
<th scope="row">Status</th>
<td class="text-truncate">@currentRound.Phase.ToString().ToSentenceCase()</td>
</tr><tr>
<th scope="row">Round id</th>
<td class="text-truncate" style="max-width: 200px" title="@currentRound.Id.ToString()">@currentRound.Id.ToString()</td>
</tr>
<tr>
<th scope="row">Mining feerate</th>
<td >@currentRound.CoinjoinState.Parameters.MiningFeeRate.ToString()</td>
</tr>
<tr>
<th scope="row">Coinjoin total inputs</th>
<td >@currentRound.CoinjoinState.Inputs.Count() inputs (@currentRound.CoinjoinState.Inputs.Sum(coin => coin.Amount.ToDecimal(MoneyUnit.BTC)) BTC)</td>
</tr>
@if (!tracker.CoinJoinClient.CoinsToRegister.IsEmpty)
{
<tr>
<th scope="row">Your inputs</th>
<td class="row" >
<span class="w-100">Registered @tracker.CoinJoinClient.CoinsInCriticalPhase.Count() inputs (@tracker.CoinJoinClient.CoinsInCriticalPhase.Sum(coin => coin.Amount.ToDecimal(MoneyUnit.BTC)) BTC) / @tracker.CoinJoinClient.CoinsToRegister.Count() inputs (@tracker.CoinJoinClient.CoinsToRegister.Sum(coin => coin.Amount.ToDecimal(MoneyUnit.BTC)) BTC) </span>
@if (tracker.BannedCoins.Any())
{
<span class="w-100 text-danger">but got @tracker.BannedCoins.Count() inputs (@tracker.BannedCoins.Sum(coin => coin.Coin.Amount.ToDecimal(MoneyUnit.BTC)) BTC) banned</span>
}
</td>
</tr>
}
@if (currentRound.Phase >= Phase.OutputRegistration)
{
<tr>
<th scope="row">Coinjoin total outputs</th>
<td >@currentRound.CoinjoinState.Outputs.Count() outputs (@currentRound.CoinjoinState.Outputs.Sum(coin => coin.Value.ToDecimal(MoneyUnit.BTC)) BTC)</td>
</tr>
if (tracker.CoinJoinClient.OutputTxOuts is { } outputs)
{
<tr>
<th scope="row">Your outputs</th>
<td >@outputs.outputTxOuts.Count() outputs (@outputs.outputTxOuts.Sum(coin => coin.Value.ToDecimal(MoneyUnit.BTC)) BTC, @outputs.batchedPayments.Count() batched payments)</td>
</tr>
}
}
</table>
</div>
@* <div class="collapse table-responsive" id="cj-@currentRound.Id"> *@
@* *@
@* <dl> *@
@* <div class="d-flex flex-wrap align-items-center gap-2"> *@
@* <dt class="w-100px"> *@
@* Status *@
@* </dt> *@
@* <dd> *@
@* @currentRound.Phase.ToString().ToSentenceCase() *@
@* </dd> *@
@* </div> *@
@* <div class="d-flex flex-wrap align-items-center gap-2"> *@
@* <dt class="w-100px"> *@
@* Round ID *@
@* </dt> *@
@* <dd> *@
@* @currentRound.Id.ToString() *@
@* </dd> *@
@* </div> *@
@* <div class="d-flex flex-wrap align-items-center gap-2"> *@
@* <dt class="w-100px"> *@
@* Mining feerate *@
@* </dt> *@
@* <dd> *@
@* @currentRound.CoinjoinState.Parameters.MiningFeeRate.ToString() *@
@* </dd> *@
@* </div> *@
@* <div class="d-flex flex-wrap align-items-center gap-2"> *@
@* <dt class="w-100px"> *@
@* Coinjoin total inputs *@
@* </dt> *@
@* <dd> *@
@* @currentRound.CoinjoinState.Inputs.Count() inputs (@currentRound.CoinjoinState.Inputs.Sum(coin => coin.Amount.ToDecimal(MoneyUnit.BTC)) BTC) *@
@* </dd> *@
@* </div> *@
@* *@
@* *@
@* @if (!tracker.CoinJoinClient.CoinsToRegister.IsEmpty) *@
@* { *@
@* <div class="d-flex flex-wrap align-items-center gap-2"> *@
@* <dt class="w-100px">Your inputs</dt> *@
@* <dd> *@
@* <span class="w-100">Registered @tracker.CoinJoinClient.CoinsInCriticalPhase.Count() inputs (@tracker.CoinJoinClient.CoinsInCriticalPhase.Sum(coin => coin.Amount.ToDecimal(MoneyUnit.BTC)) BTC) / @tracker.CoinJoinClient.CoinsToRegister.Count() inputs (@tracker.CoinJoinClient.CoinsToRegister.Sum(coin => coin.Amount.ToDecimal(MoneyUnit.BTC)) BTC) </span> *@
@* @if (tracker.BannedCoins.Any()) *@
@* { *@
@* <span class="w-100 text-danger">but got @tracker.BannedCoins.Count() inputs (@tracker.BannedCoins.Sum(coin => coin.Coin.Amount.ToDecimal(MoneyUnit.BTC)) BTC) banned</span> *@
@* } *@
@* </dd> *@
@* </div> *@
@* } *@
@* *@
@* @if (currentRound.Phase >= Phase.OutputRegistration) *@
@* { *@
@* <div class="d-flex flex-wrap align-items-center gap-2"> *@
@* <dt class="w-100px">Coinjoin total outputs</dt> *@
@* <dd> *@
@* @currentRound.CoinjoinState.Outputs.Count() outputs (@currentRound.CoinjoinState.Outputs.Sum(coin => coin.Value.ToDecimal(MoneyUnit.BTC)) BTC) *@
@* </dd> *@
@* </div> *@
@* if (tracker.CoinJoinClient.OutputTxOuts is { } outputs) *@
@* { *@
@* <div class="d-flex flex-wrap align-items-center gap-2"> *@
@* <dt class="w-100px">>Your outputs</dt> *@
@* <dd> *@
@* @outputs.outputTxOuts.Count() outputs (@outputs.outputTxOuts.Sum(coin => coin.Value.ToDecimal(MoneyUnit.BTC)) BTC, @outputs.batchedPayments.Count() batched payments) *@
@* </dd> *@
@* </div> *@
@* } *@
@* } *@
@* </dl> *@
@* *@
@* </div> *@
}
}
</div>
}
}
@@ -326,8 +448,6 @@
<button type="button" class="btn btn-text p-1" data-bs-toggle="modal" data-bs-target="#coins">
View coins
</button>
</div>
</div>
}

View File

@@ -1,5 +1,4 @@

@using WalletWasabi.Backend.Controllers
@using BTCPayServer.Plugins.Wabisabi
@model WalletWasabi.Backend.Controllers.WabisabiCoordinatorSettings
@@ -8,9 +7,19 @@
ViewData["NavPartialName"] = "../UIServer/_Nav";
}
<h2 class="mb-4">Coinjoin coordinator configuration</h2>
<form method="post">
<div class="d-flex align-items-center justify-content-between mb-3">
<h3 class="mb-0">
<span>Coinjoin coordinator</span>
<a href="https://docs.btcpayserver.org/Wabisabi" target="_blank" rel="noreferrer noopener" title="More information...">
<vc:icon symbol="info"/>
</a>
</h3>
<button name="command" type="submit" value="save" class="btn btn-primary mt-3 mt-sm-0">Save</button>
</div>
<div class="row">
<div class="col-xxl-constrain col-xl-8">
<div class="form-group form-check">
@@ -36,9 +45,8 @@
</textarea>
</div>
</div>
</div>
<div class="row ">
<div class="col-xxl-constrain col-xl-8">
<div class="col-xxl-constrain col-xl-4">
<h3 class="mb-3">Publish to Nostr </h3>
<div class="form-group ">
<label asp-for="NostrRelay" class="form-label">Nostr Relay</label>
@@ -68,7 +76,6 @@
<p class=" alert alert-warning" style="white-space: pre-line">
@WabisabiCoordinatorConfigController.OurDisclaimer
</p>
<button name="command" type="submit" value="save" class="btn btn-primary mt-2">Save</button>
</form>

View File

@@ -1,15 +1,25 @@

@using BTCPayServer.Components
@using BTCPayServer.Abstractions.Extensions
@using BTCPayServer.Views.Stores
@using BTCPayServer.Components
@using BTCPayServer
@model BTCPayServer.Plugins.Wabisabi.CoinjoinsViewModel
@{
ViewData.SetActivePage(StoreNavPages.Plugins);
var storeId = Context.GetCurrentStoreId();
ViewData.SetActivePage("CoinjoinHistory", "Coinjoin", "Coinjoin History", storeId);
}
<div class="d-flex align-items-center justify-content-between mb-3">
<h3 class="mb-0">Coinjoin History</h3>
<h3 class="mb-0">
<span>@ViewData["Title"]</span>
<a href="https://docs.btcpayserver.org/Wabisabi" target="_blank" rel="noreferrer noopener" title="More information...">
<vc:icon symbol="info"/>
</a>
</h3>
<a asp-action="UpdateWabisabiStoreSettings" asp-route-storeId="@storeId" class="btn btn-primary mt-3 mt-sm-0" role="button">
<span class="fa fa-settings"></span>
Settings
</a>
</div>
<partial name="Wabisabi/CoinjoinHistoryTable" model="Model.Coinjoins.ToList()" />
<partial name="Wabisabi/CoinjoinHistoryTable" model="Model.Coinjoins.ToList()"/>
<vc:pager view-model="Model"></vc:pager>

View File

@@ -17,9 +17,7 @@
@inject BTCPayServerOptions BtcPayServerOptions
@{
var storeId = _scopeProvider.GetCurrentStoreId();
Layout = "../Shared/_NavLayout.cshtml";
ViewData["NavPartialName"] = "../UIStores/_Nav";
ViewData.SetActivePage("Plugins", "BTCPayServer.Views.Stores.StoreNavPages", "Wabisabi coinjoin support", storeId);
ViewData.SetActivePage("CoinjoinSettings", "Coinjoin", "Coinjoin settings", storeId);
var userid = Context.User.Claims.Single(claim => claim.Type == ClaimTypes.NameIdentifier).Value;
var anyEnabled = Model.Settings.Any(settings => settings.Enabled);
ScriptPubKeyType? scriptType;
@@ -34,28 +32,45 @@
.Select(pair => new SelectListItem(pair.Value.s.StoreName, pair.Key, Model.MixToOtherWallet == pair.Key)).Prepend(new SelectListItem("None", ""));
}
<div class="row">
<div class="d-flex">
<h2 class="">Coinjoin configuration</h2>
<a href="https://github.com/Kukks/BTCPayServerPlugins/blob/master/Plugins/BTCPayServer.Plugins.Wabisabi/readme.md" class="ms-1" target="_blank" rel="noreferrer noopener">
<span class="fa fa-question-circle-o text-secondary" title="More information..."></span>
<form method="post">
<partial name="_StatusMessage"/>
<div class="d-flex align-items-center justify-content-between mb-3">
<h3 class="mb-0">
<span>@ViewData["Title"]</span>
<a href="https://docs.btcpayserver.org/Wabisabi" target="_blank" rel="noreferrer noopener" title="More information...">
<vc:icon symbol="info"/>
</a>
</h3>
<div>
<button name="command" type="submit" value="save" class="btn btn-primary mt-3 mt-sm-0">Save</button>
<a asp-action="ListCoinjoins" asp-route-storeId="@storeId" class="btn btn-secondary mt-3 mt-sm-0" role="button">
Coinjoin History
</a>
<button type="button" class="btn btn-secondary mt-3 mt-sm-0" permission="@Policies.CanModifyServerSettings"
data-bs-toggle="modal" data-bs-target="#discover-prompt">
Add Coordinator
</button>
<a asp-controller="WabisabiCoordinatorConfig" asp-action="UpdateWabisabiSettings" class="btn btn-secondary mt-3 mt-sm-0" permission="@Policies.CanModifyServerSettings">Coordinator</a>
@* <a class="btn btn-secondary mt-3 mt-sm-0" href="https://gist.github.com/nopara73/bb17e89d7dc9af536ca41f50f705d329" rel="noreferrer noopener" target="_blank">Enable Discreet payments - Coming soon</a> *@
</div>
</div>
<form method="post">
@{
if (BtcPayServerOptions.SocksEndpoint is null)
{
<div class="alert alert-danger d-flex align-items-center" role="alert">
<vc:icon symbol="warning"/>
<span class="ms-3">TOR is not configured on this BTCPay Server instance. All communication will be over clearnet and therefore not private!</span>
</div>
<div class="alert alert-danger d-flex align-items-center" role="alert">
<vc:icon symbol="warning"/>
<span class="ms-3">TOR is not configured on this BTCPay Server instance. All communication will be over clearnet and therefore not private!</span>
</div>
}
var wallet = await WalletProvider.GetWalletAsync(storeId);
if (wallet is BTCPayWallet)
{
@if (!((BTCPayKeyChain) wallet.KeyChain).KeysAvailable)
{
<div class="alert alert-danger d-flex align-items-center" role="alert">
@@ -217,7 +232,7 @@
<span class="text-muted">@coordinator.Coordinator</span>
<div>
<div>@(!coordinator.WasabiCoordinatorStatusFetcher.Connected? "Coordinator Status: Not connected": "Coordinator Status: Connected")</div>
<div>@(!coordinator.WasabiCoordinatorStatusFetcher.Connected ? "Coordinator Status: Not connected" : "Coordinator Status: Connected")</div>
@if (!string.IsNullOrEmpty(coordinator.Description))
{
@@ -303,7 +318,7 @@
By enabling this coordinator, you agree to their terms and conditions.
</a>
</div>
</div>
@{
var canEnable = coordinator.WasabiCoordinatorStatusFetcher.Connected && coordinator.RoundStateUpdater.AnyRound;
@@ -346,15 +361,10 @@
}
<button name="command" type="submit" value="save" class="btn btn-primary mt-2">Save</button>
<a asp-controller="WabisabiStore" asp-action="ListCoinjoins" class="btn btn-secondary mt-2" asp-route-storeId="@storeId">Coinjoins</a>
</form>
<a asp-controller="WabisabiCoordinatorConfig" asp-action="UpdateWabisabiSettings" class="btn btn-secondary mt-2" permission="@Policies.CanModifyServerSettings">Coordinator runner</a>
<partial name="Wabisabi/AddCoordinatorPrompt" model="@(new DiscoveredCoordinator())"/>
<a class="btn btn-secondary mt-2" href="https://gist.github.com/nopara73/bb17e89d7dc9af536ca41f50f705d329" rel="noreferrer noopener" target="_blank">Enable Discreet payments - Coming soon</a>
@section PageFootContent {

View File

@@ -21,7 +21,6 @@ using WalletWasabi.WabiSabi.Client.RoundStateAwaiters;
using WalletWasabi.WabiSabi.Client.StatusChangedEvents;
using WalletWasabi.Wallets;
using WalletWasabi.WebClients.Wasabi;
using HttpClientFactory = WalletWasabi.WebClients.Wasabi.HttpClientFactory;
namespace BTCPayServer.Plugins.Wabisabi;
@@ -137,7 +136,7 @@ public class WabisabiCoordinatorClientInstance
public Uri Coordinator { get; set; }
public WalletProvider WalletProvider { get; }
public string TermsConditions { get; set; }
public HttpClientFactory WasabiHttpClientFactory { get; set; }
public WasabiHttpClientFactory WasabiHttpClientFactory { get; set; }
public RoundStateUpdater RoundStateUpdater { get; set; }
public CoinPrison CoinPrison { get; private set; }
public WasabiCoordinatorStatusFetcher WasabiCoordinatorStatusFetcher { get; set; }
@@ -176,7 +175,7 @@ public class WabisabiCoordinatorClientInstance
}
else
{
WasabiHttpClientFactory = new HttpClientFactory(torEndpoint, () => Coordinator);
WasabiHttpClientFactory = new WasabiHttpClientFactory(torEndpoint, () => Coordinator);
var roundStateUpdaterCircuit = new PersonCircuit();
var roundStateUpdaterHttpClient =
WasabiHttpClientFactory.NewHttpClient(Mode.SingleCircuitPerLifetime, roundStateUpdaterCircuit);

View File

@@ -1,4 +1,5 @@
using System;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using BTCPayServer.Abstractions.Contracts;
using BTCPayServer.Abstractions.Models;
@@ -20,7 +21,7 @@ public class WabisabiPlugin : BaseBTCPayServerPlugin
{
public override IBTCPayServerPlugin.PluginDependency[] Dependencies { get; } =
{
new() { Identifier = nameof(BTCPayServer), Condition = ">=1.11.0" }
new() { Identifier = nameof(BTCPayServer), Condition = ">=1.11.5" }
};
public override void Execute(IServiceCollection applicationBuilder)
{
@@ -140,3 +141,4 @@ public class WabisabiPlugin : BaseBTCPayServerPlugin
base.Execute(applicationBuilder, applicationBuilderApplicationServices);
}
}

View File

@@ -1,3 +1,4 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
@@ -33,7 +34,7 @@ namespace BTCPayServer.Plugins.Wabisabi
_memoryCache = memoryCache;
}
private string GetCacheKey(string storeId)
public static string GetCacheKey(string storeId)
{
return $"{nameof(WabisabiStoreSettings)}-{storeId}";
}
@@ -76,7 +77,7 @@ namespace BTCPayServer.Plugins.Wabisabi
_walletProvider.LoadedWallets.TryGetValue(storeId, out var walletTask);
if (walletTask != null)
{
var wallet = await walletTask;
var wallet = await walletTask.Value;
await _coordinatorClientInstanceManager.StopWallet(wallet, setting.Coordinator);
}
}
@@ -136,15 +137,29 @@ namespace BTCPayServer.Plugins.Wabisabi
}
public async Task<List<BTCPayWallet.CoinjoinData>> GetCoinjoinHistory(string storeId)
public async Task<List<BTCPayWallet.CoinjoinData>> GetCoinjoinHistory(string storeId, bool force = false)
{
return (await _walletRepository.GetWalletObjects(
new GetWalletObjectsQuery(new WalletId(storeId, "BTC"))
{
Type = "coinjoin"
})).Values.Where(data => !string.IsNullOrEmpty(data.Data))
.Select(data => JObject.Parse(data.Data).ToObject<BTCPayWallet.CoinjoinData>())
.OrderByDescending(tuple => tuple.Timestamp).ToList();
var k = GetCacheKey(storeId) + "cjhistory";
if (force)
{
_memoryCache.Remove(k);
}
return await _memoryCache.GetOrCreateAsync(k, async entry =>
{
entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(1);
var result = (await _walletRepository.GetWalletObjects(
new GetWalletObjectsQuery(new WalletId(storeId, "BTC"))
{
Type = "coinjoin"
})).Values.Where(data => !string.IsNullOrEmpty(data.Data))
.Select(data => JObject.Parse(data.Data).ToObject<BTCPayWallet.CoinjoinData>())
.OrderByDescending(tuple => tuple.Timestamp).ToList();
entry.Value = result;
return result;
});
}
}

View File

@@ -14,6 +14,7 @@ using BTCPayServer.HostedServices;
using BTCPayServer.Payments.PayJoin;
using BTCPayServer.Services;
using BTCPayServer.Services.Stores;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using NBitcoin;
@@ -57,7 +58,7 @@ public class WalletProvider : PeriodicRunner,IWalletProvider
_networkProvider = networkProvider;
}
public readonly ConcurrentDictionary<string, Task<IWallet?>> LoadedWallets = new();
public readonly ConcurrentDictionary<string, Lazy<Task<IWallet>>> LoadedWallets = new();
public class WalletUnloadEventArgs : EventArgs
{
@@ -73,7 +74,7 @@ public class WalletProvider : PeriodicRunner,IWalletProvider
public async Task<IWallet?> GetWalletAsync(string name)
{
await initialLoad.Task;
return await LoadedWallets.GetOrAddAsync(name, async s =>
return await Smartifier.GetOrCreate(LoadedWallets, name, async () =>
{
if (!_cachedSettings.TryGetValue(name, out var wabisabiStoreSettings))
{
@@ -101,7 +102,7 @@ public class WalletProvider : PeriodicRunner,IWalletProvider
var accountKeyPath2 = await explorerClient.GetMetadataAsync<RootedKeyPath>(derivationStrategy,
WellknownMetadataKeys.AccountKeyPath);
accountKeyPath = accountKeyPath2 ?? accountKeyPath;
var smartifier = new Smartifier(_serviceProvider.GetRequiredService<WalletRepository>(),
var smartifier = new Smartifier(_logger,_serviceProvider.GetRequiredService<WalletRepository>(),
explorerClient, derivationStrategy, name, UtxoLocker, accountKeyPath);
if (masterKey is null || accountKey is null || accountKeyPath is null)
{
@@ -112,7 +113,7 @@ public class WalletProvider : PeriodicRunner,IWalletProvider
}
else
{
var smartifier = new Smartifier(_serviceProvider.GetRequiredService<WalletRepository>(), explorerClient,
var smartifier = new Smartifier(_logger,_serviceProvider.GetRequiredService<WalletRepository>(), explorerClient,
derivationStrategy, name, UtxoLocker, accountKeyPath);
keychain = new BTCPayKeyChain(explorerClient, derivationStrategy, null, null, smartifier);
}
@@ -127,9 +128,11 @@ public class WalletProvider : PeriodicRunner,IWalletProvider
_serviceProvider.GetRequiredService<PullPaymentHostedService>(),derivationStrategy, explorerClient, keychain,
name, wabisabiStoreSettings, UtxoLocker,
_loggerFactory,
_serviceProvider.GetRequiredService<StoreRepository>());
_serviceProvider.GetRequiredService<StoreRepository>(),
_serviceProvider.GetRequiredService<IMemoryCache>()
);
});
}, _logger);
}
@@ -231,7 +234,7 @@ public class WalletProvider : PeriodicRunner,IWalletProvider
LoadedWallets.TryRemove(name, out var walletTask);
if (walletTask != null)
{
var wallet = await walletTask;
var wallet = await walletTask.Value;
WalletUnloaded?.Invoke(this, new WalletUnloadEventArgs(wallet));
}
}
@@ -247,7 +250,7 @@ public class WalletProvider : PeriodicRunner,IWalletProvider
{
_cachedSettings.AddOrReplace(storeId, wabisabiSettings);
var btcpayWalet = (BTCPayWallet) await existingWallet;
var btcpayWalet = (BTCPayWallet) await existingWallet.Value;
if (btcpayWalet is null)
{