Improve everything

This commit is contained in:
Kukks
2023-11-15 15:24:21 +01:00
parent 206875b133
commit 83807d18b8
16 changed files with 233 additions and 95 deletions

View File

@@ -71,17 +71,43 @@ public class BTCPayCoinjoinCoinSelector : IRoundCoinSelector
_wallet.BatchPayments
? await _wallet.DestinationProvider.GetPendingPaymentsAsync(utxoSelectionParameters)
: Array.Empty<PendingPayment>();
var minCoins = new Dictionary<AnonsetType, int>();
var maxPerType = new Dictionary<AnonsetType, int>();
var attemptingTobeParanoid = payments.Any() && _wallet.WabisabiStoreSettings.ParanoidPayments;
var attemptingToMixToOtherWallet = string.IsNullOrEmpty(_wallet.WabisabiStoreSettings.MixToOtherWallet);
selectCoins:
maxPerType.Clear();
if (attemptingTobeParanoid || attemptingToMixToOtherWallet)
{
maxPerType.Add(AnonsetType.Red,0);
maxPerType.Add(AnonsetType.Orange,0);
}
if (_wallet.RedCoinIsolation)
{
minCoins.Add(AnonsetType.Red, 1);
maxPerType.TryAdd(AnonsetType.Red, 1);
}
var solution = SelectCoinsInternal(utxoSelectionParameters, coinCandidates, payments,
Random.Shared.Next(10, 31),
minCoins,
maxPerType,
new Dictionary<AnonsetType, int>() {{AnonsetType.Red, 1}, {AnonsetType.Orange, 1}, {AnonsetType.Green, 1}},
_wallet.ConsolidationMode, liquidityClue, secureRandom);
if (attemptingTobeParanoid && !solution.HandledPayments.Any())
{
attemptingTobeParanoid = false;
payments = Array.Empty<PendingPayment>();
goto selectCoins;
}
if (attemptingToMixToOtherWallet && !solution.Coins.Any())
{
// check that we have enough coins to mix to other wallet
attemptingToMixToOtherWallet = false;
goto selectCoins;
}
_logger.LogTrace(solution.ToString());
return solution.Coins.ToImmutableList();
}

View File

@@ -13,7 +13,7 @@
<PropertyGroup>
<Product>Wabisabi Coinjoin</Product>
<Description>Allows you to integrate your btcpayserver store with coinjoins.</Description>
<Version>1.0.63</Version>
<Version>1.0.64</Version>
</PropertyGroup>
<!-- Plugin development properties -->

View File

@@ -15,6 +15,7 @@ using BTCPayServer.Payments.PayJoin;
using BTCPayServer.Services;
using BTCPayServer.Services.Stores;
using BTCPayServer.Services.Wallets;
using LinqKit;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Logging;
using NBitcoin;
@@ -92,7 +93,7 @@ public class BTCPayWallet : IWallet, IDestinationProvider
public string StoreId { get; set; }
public string WalletName => StoreId;
public bool IsUnderPlebStop => false;
public bool IsUnderPlebStop => !WabisabiStoreSettings.Active;
bool IWallet.IsMixable(string coordinator)
{
@@ -114,7 +115,9 @@ public class BTCPayWallet : IWallet, IDestinationProvider
public async Task<bool> IsWalletPrivateAsync()
{
return !BatchPayments && await GetPrivacyPercentageAsync()>= 1;
return !BatchPayments && await GetPrivacyPercentageAsync() >= 1 && (WabisabiStoreSettings.PlebMode ||
string.IsNullOrEmpty(WabisabiStoreSettings
.MixToOtherWallet));
}
public async Task<double> GetPrivacyPercentageAsync()
@@ -127,11 +130,18 @@ public class BTCPayWallet : IWallet, IDestinationProvider
await _savingProgress;
var utxos = await _btcPayWallet.GetUnspentCoins(DerivationScheme);
var utxoLabels = await GetUtxoLabels(_walletRepository, StoreId,utxos);
var utxoLabels = await GetUtxoLabels(_memoryCache ,_walletRepository, StoreId,utxos, false);
await _smartifier.LoadCoins(utxos.ToList(), 1, utxoLabels);
var coins = await Task.WhenAll(_smartifier.Coins.Where(pair => utxos.Any(data => data.OutPoint == pair.Key))
.Select(pair => pair.Value));
foreach (var c in coins)
{
var utxo = utxos.Single(coin => coin.OutPoint == c.Outpoint);
c.Height = utxo.Confirmations > 0 ? new Height((uint) utxo.Confirmations) : Height.Mempool;
}
return new CoinsView(coins);
}
@@ -164,6 +174,25 @@ public class BTCPayWallet : IWallet, IDestinationProvider
var coordSettings = WabisabiStoreSettings.Settings.Find(settings => settings.Coordinator == coordinatorName && settings.Enabled);
return coordSettings is not null && IsRoundOk(roundParameters, coordSettings);
}
public async Task CompletedCoinjoin(CoinJoinTracker finishedCoinJoin)
{
try
{
var successfulCoinJoinResult = (await finishedCoinJoin.CoinJoinTask) as SuccessfulCoinJoinResult;
await RegisterCoinjoinTransaction(successfulCoinJoinResult,
finishedCoinJoin.CoinJoinClient.CoordinatorName);
}
catch (Exception e)
{
}
}
public static bool IsRoundOk(RoundParameters roundParameters, WabisabiStoreCoordinatorSettings coordSettings)
{
try
@@ -183,20 +212,15 @@ public class BTCPayWallet : IWallet, IDestinationProvider
{
try
{
await _savingProgress;
}
catch (Exception e)
{
}
try
{
if (IsUnderPlebStop)
{
return Array.Empty<SmartCoin>();
}
var utxos = await _btcPayWallet.GetUnspentCoins(DerivationScheme, true, CancellationToken.None);
var utxoLabels = await GetUtxoLabels(_walletRepository, StoreId,utxos);
var utxoLabels = await GetUtxoLabels(_memoryCache, _walletRepository, StoreId,utxos, false);
if (!WabisabiStoreSettings.PlebMode)
{
if (WabisabiStoreSettings.InputLabelsAllowed?.Any() is true)
@@ -239,7 +263,7 @@ public class BTCPayWallet : IWallet, IDestinationProvider
foreach (SmartCoin c in resultX)
{
var utxo = utxos.Single(coin => coin.OutPoint == c.Outpoint);
c.Height = new Height((uint) utxo.Confirmations);
c.Height = utxo.Confirmations > 0 ? new Height((uint) utxo.Confirmations) : Height.Mempool;
}
return resultX;
@@ -251,12 +275,30 @@ public class BTCPayWallet : IWallet, IDestinationProvider
}
}
public static async Task<Dictionary<OutPoint, (HashSet<string> labels, double anonset, CoinjoinData coinjoinData)>> GetUtxoLabels(WalletRepository walletRepository, string storeId ,ReceivedCoin[] utxos)
{
var walletTransactionsInfoAsync = await walletRepository.GetWalletTransactionsInfo(new WalletId(storeId, "BTC"),
utxos.SelectMany(GetWalletObjectsQuery.Get).Distinct().ToArray());
var utxoLabels = utxos.Select(coin =>
public static async Task<Dictionary<OutPoint, (HashSet<string> labels, double anonset, CoinjoinData coinjoinData)>> GetUtxoLabels(IMemoryCache memoryCache, WalletRepository walletRepository, string storeId ,ReceivedCoin[] utxos, bool isDepth)
{
var utxoToQuery = utxos.ToArray();
var cacheResult = new Dictionary<OutPoint, (HashSet<string> labels, double anonset, CoinjoinData coinjoinData)>();
foreach (var utxo in utxoToQuery)
{
if (memoryCache.TryGetValue<(HashSet<string> labels, double anonset, CoinjoinData coinjoinData)>(
$"wabisabi_{utxo.OutPoint}_utxo", out var cacheVariant ) )
{
if (!cacheResult.TryAdd(utxo.OutPoint, cacheVariant))
{
//wtf!
}
}
}
utxoToQuery = utxoToQuery.Where(utxo => !cacheResult.ContainsKey(utxo.OutPoint)).ToArray();
var walletTransactionsInfoAsync = await walletRepository.GetWalletTransactionsInfo(new WalletId(storeId, "BTC"),
utxoToQuery.SelectMany(GetWalletObjectsQuery.Get).Distinct().ToArray());
var utxoLabels = utxoToQuery.Select(coin =>
{
walletTransactionsInfoAsync.TryGetValue(coin.OutPoint.Hash.ToString(), out var info1);
walletTransactionsInfoAsync.TryGetValue(coin.Address.ToString(), out var info2);
@@ -268,25 +310,23 @@ public class BTCPayWallet : IWallet, IDestinationProvider
}
return (coin.OutPoint, info);
}).Where(tuple => tuple.info is not null)
.ToDictionary(tuple => tuple.OutPoint, tuple => tuple.info);
return utxoLabels.ToDictionary(pair => pair.Key, pair =>
}).Where(tuple => tuple.info is not null).DistinctBy(tuple => tuple.OutPoint)
.ToDictionary(tuple => tuple.OutPoint, pair =>
{
var labels = new HashSet<string>();
if (pair.Value.LabelColors.Any())
if (pair.info.LabelColors.Any())
{
labels.AddRange((pair.Value.LabelColors.Select(pair => pair.Key)));
labels.AddRange((pair.info.LabelColors.Select(pair => pair.Key)));
}
if (pair.Value.Attachments.Any() is true)
if (pair.info.Attachments.Any() is true)
{
labels.AddRange((pair.Value.Attachments.Select(attachment => attachment.Id)));
labels.AddRange((pair.info.Attachments.Select(attachment => attachment.Id)));
}
var cjData = pair.Value.Attachments
var cjData = pair.info.Attachments
.FirstOrDefault(attachment => attachment.Type == "coinjoin")?.Data
?.ToObject<CoinjoinData>();
var explicitAnonset = pair.Value.Attachments.FirstOrDefault(attachment => attachment.Type == "anonset")
var explicitAnonset = pair.info.Attachments.FirstOrDefault(attachment => attachment.Type == "anonset")
?.Id;
double anonset = 1;
if (!string.IsNullOrEmpty(explicitAnonset))
@@ -294,7 +334,7 @@ public class BTCPayWallet : IWallet, IDestinationProvider
anonset = double.Parse(explicitAnonset);
}else if (cjData is not null)
{
var utxo = cjData.CoinsOut.FirstOrDefault(dataCoin => dataCoin.Outpoint == pair.Key.ToString());
var utxo = cjData.CoinsOut.FirstOrDefault(dataCoin => dataCoin.Outpoint == pair.OutPoint.ToString());
if (utxo is not null)
{
anonset = utxo.AnonymitySet;
@@ -305,6 +345,11 @@ public class BTCPayWallet : IWallet, IDestinationProvider
return (labels, anonset, cjData);
});
foreach (var pair in utxoLabels)
{
memoryCache.Set($"wabisabi_{pair.Key.Hash}_utxo", pair.Value, isDepth? TimeSpan.FromMinutes(10): TimeSpan.FromMinutes(5));
}
return utxoLabels.Concat(cacheResult).ToDictionary(pair => pair.Key, pair => pair.Value);
}
@@ -389,7 +434,7 @@ public class BTCPayWallet : IWallet, IDestinationProvider
{
coin.HdPubKey.SetKeyState(KeyState.Used);
coin.SpenderTransaction = smartTx;
smartTx.TryAddWalletInput(coin);
smartTx.TryAddWalletInput(SmartCoin.Clone(coin));
});
result.Outputs.ForEach(s =>
{
@@ -422,7 +467,7 @@ public class BTCPayWallet : IWallet, IDestinationProvider
Round = result.RoundId.ToString(),
CoordinatorName = coordinatorName,
Transaction = txHash.ToString(),
CoinsIn = smartTx.WalletInputs.Select(coin => new CoinjoinData.CoinjoinDataCoin()
CoinsIn = result.Coins.Select(coin => new CoinjoinData.CoinjoinDataCoin()
{
AnonymitySet = coin.AnonymitySet,
PayoutId = null,
@@ -503,6 +548,12 @@ public class BTCPayWallet : IWallet, IDestinationProvider
}
_smartifier.SmartTransactions.AddOrReplace(txHash, Task.FromResult(smartTx));
smartTx.WalletOutputs.ForEach(coin =>
{
_smartifier.Coins.AddOrReplace(coin.Outpoint, Task.FromResult(coin));
});
//
// var kp = await ExplorerClient.GetMetadataAsync<RootedKeyPath>(DerivationScheme,
// WellknownMetadataKeys.AccountKeyPath);

View File

@@ -17,7 +17,7 @@ public static class CoordinatorExtensions
services.AddTransient(provider =>
{
var s = provider.GetRequiredService<WabisabiCoordinatorService>();
if (!s.Started)
if (!s.Started )
{
return null;
}

View File

@@ -3,11 +3,14 @@ using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using Newtonsoft.Json;
using WalletWasabi.Blockchain.TransactionOutputs;
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]));

View File

@@ -8,6 +8,7 @@ using BTCPayServer.Abstractions.Contracts;
using BTCPayServer.Payments.PayJoin;
using BTCPayServer.Services;
using BTCPayServer.Services.Wallets;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Logging;
using NBitcoin;
using NBXplorer;
@@ -24,6 +25,7 @@ namespace BTCPayServer.Plugins.Wabisabi;
public class Smartifier
{
private readonly IMemoryCache _memoryCache;
private readonly ILogger _logger;
private readonly WalletRepository _walletRepository;
private readonly ExplorerClient _explorerClient;
@@ -32,11 +34,13 @@ public class Smartifier
private readonly IUTXOLocker _utxoLocker;
public Smartifier(
IMemoryCache memoryCache,
ILogger logger,
WalletRepository walletRepository,
ExplorerClient explorerClient, DerivationStrategyBase derivationStrategyBase, string storeId,
IUTXOLocker utxoLocker, RootedKeyPath accountKeyPath)
{
_memoryCache = memoryCache;
_logger = logger;
_walletRepository = walletRepository;
_explorerClient = explorerClient;
@@ -44,7 +48,32 @@ public class Smartifier
_storeId = storeId;
_utxoLocker = utxoLocker;
_accountKeyPath = accountKeyPath;
_ = LoadInitialTxs();
}
private async Task LoadInitialTxs()
{
try
{
var txsBulk = await _explorerClient.GetTransactionsAsync(DerivationScheme);
foreach (var transactionInformation in txsBulk.ConfirmedTransactions.Transactions.Concat(txsBulk.UnconfirmedTransactions.Transactions))
{
TransactionInformations.AddOrReplace(transactionInformation.TransactionId,
new Lazy<Task<TransactionInformation>>(() => Task.FromResult(transactionInformation)));
}
}
finally
{
_loadInitialTxs.TrySetResult();
}
}
private TaskCompletionSource _loadInitialTxs = 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();
@@ -95,6 +124,8 @@ public class Smartifier
public async Task LoadCoins(List<ReceivedCoin> coins, int current ,
Dictionary<OutPoint, (HashSet<string> labels, double anonset, BTCPayWallet.CoinjoinData coinjoinData)> utxoLabels)
{
await _loadInitialTxs.Task;
coins = coins.Where(data => data is not null).ToList();
if (current > 3)
{
@@ -168,7 +199,7 @@ public class Smartifier
};
}).Where(receivedCoin => receivedCoin is not null).ToList();
await LoadCoins(inputsToLoad,current+1, await BTCPayWallet.GetUtxoLabels(_walletRepository, _storeId, inputsToLoad.ToArray()));
await LoadCoins(inputsToLoad,current+1, await BTCPayWallet.GetUtxoLabels( _memoryCache ,_walletRepository, _storeId, inputsToLoad.ToArray(), true ));
foreach (MatchedOutput input in unsmartTx.Inputs)
{
if (!ourSpentUtxos.TryGetValue(input, out var outputtxin))

View File

@@ -2,6 +2,7 @@
@using BTCPayServer.Common
@using BTCPayServer.Plugins.Wabisabi
@using NBitcoin
@using Org.BouncyCastle.Asn1.Ocsp
@using WalletWasabi.Extensions
@using WalletWasabi.WabiSabi.Backend.Rounds
@using WalletWasabi.WabiSabi.Client
@@ -100,7 +101,7 @@
function getColor(isPrivate, score, maxScore) {
let normalizedScore = Math.min(Math.max(score, 0), maxScore) / maxScore;
return isPrivate ? `rgb(0, ${Math.floor(255 * normalizedScore)}, 0)` : `rgb(255, ${Math.floor(128 * normalizedScore)}, 0)`;
return isPrivate ? `rgb(81, 177, 62)` : `rgb(255, ${Math.floor(128 * normalizedScore)}, 0)`;
}
function prepareDatasets(data) {
@@ -122,8 +123,8 @@ function prepareDatasets(data) {
id: "inprogresscoins",
label: "In Progress Coins",
data: inProgressCoins.map(coin => coin.value),
backgroundColor: inProgressCoins.map(() => "rgba(81, 177, 62,1)"),
alternativeBackgroundColor: inProgressCoins.map(() => "rgba(30, 122, 68,1)"),
backgroundColor: inProgressCoins.map(() => "rgb(56, 151, 37)"),
alternativeBackgroundColor: inProgressCoins.map(() => "rgb(28, 113, 11)"),
borderColor: "transparent",
borderWidth: 3,
coins: inProgressCoins
@@ -286,16 +287,21 @@ updateInProgressAnimation(myChart);
}
<header>
<h4>Coinjoin stats</h4>
<a asp-controller="WabisabiStore" asp-action="UpdateWabisabiStoreSettings" asp-route-storeId="@storeId" class="fw-semibold">
Configure coinjoin settings
</a>
<div class="d-flex gap-1">
<a asp-controller="WabisabiStore" asp-action="ToggleActive" asp-route-storeId="@storeId" asp-route-returnUrl="@Context.Request.GetCurrentUrl()" class="fw-semibold">
@(settings.Active ? "Deactivate" : "Activate")
</a>
-
<a asp-controller="WabisabiStore" asp-action="UpdateWabisabiStoreSettings" asp-route-storeId="@storeId" class="fw-semibold">
Configure
</a>
</div>
</header>
<div class="w-100">
@if (coins.Any())
{
<div class="d-flex justify-content-center" style="max-height: 400px; "> <canvas id="cjchart" class="mb-4"></canvas></div>
<div class="d-flex justify-content-center mb-4" style="max-height: 400px; "> <canvas id="cjchart"></canvas></div>
}
@* <div> *@
@@ -508,10 +514,15 @@ updateInProgressAnimation(myChart);
</tr>
@if (!tracker.CoinJoinClient.CoinsToRegister.IsEmpty)
{
var statement = $"Registered {tracker.CoinJoinClient.CoinsInCriticalPhase.Count()} inputs ({tracker.CoinJoinClient.CoinsInCriticalPhase.Sum(coin => coin.Amount.ToDecimal(MoneyUnit.BTC))} BTC)";
if (tracker.CoinJoinClient.CoinsInCriticalPhase.Count() != tracker.CoinJoinClient.CoinsToRegister.Count())
{
statement += $" / {tracker.CoinJoinClient.CoinsToRegister.Count()} inputs ({tracker.CoinJoinClient.CoinsToRegister.Sum(coin => coin.Amount.ToDecimal(MoneyUnit.BTC))} BTC)";
}
<tr>
<th scope="">Your inputs</th>
<td class="">
<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>
<span class="w-100">@statement</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>
@@ -528,9 +539,11 @@ updateInProgressAnimation(myChart);
</tr>
if (tracker.CoinJoinClient.OutputTxOuts is { } outputs)
{
var statement = $"{outputs.outputTxOuts.Count()} outputs ({outputs.outputTxOuts.Sum(coin => coin.Value.ToDecimal(MoneyUnit.BTC))} BTC {(outputs.batchedPayments.Any()? $"{outputs.batchedPayments.Count()} batched payments": "")}";
<tr>
<th scope="">Your outputs</th>
<td >@outputs.outputTxOuts.Count() outputs (@outputs.outputTxOuts.Sum(coin => coin.Value.ToDecimal(MoneyUnit.BTC)) BTC, @outputs.batchedPayments.Count() batched payments)</td>
<td >@statement</td>
</tr>
}
}

View File

@@ -35,7 +35,7 @@
@if (ViewData.ModelState.TryGetValue("config", out var error) && error.Errors.Any())
{
<span class="text-danger">@string.Join("\n", error.Errors)</span>
<span class="text-danger">@string.Join("\n", error.Errors.Select(modelError => modelError.ErrorMessage))</span>
}
<textarea rows="10" cols="40" class="form-control" id="config" name="config" >
@Html.Raw(ViewBag.Config)

View File

@@ -44,19 +44,22 @@
<vc:icon symbol="info"/>
</a>
</h3>
<div>
<div class="d-flex align-items-center gap-1 ">
<div class="d-flex align-items-center">
<input asp-for="Active" type="checkbox" class="btcpay-toggle me-2"/>
<label asp-for="Active" class="form-label mb-0 me-1"></label>
</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>
<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> *@
@* <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>
@{
@@ -80,9 +83,26 @@
}
}
}
<style>
#blocker:hover{
background-color: rgba(128,128,128, 0.5);
}
#blocker:hover h4{
display: block !important;
left: 0;
position: fixed;
width: 100%
}
</style>
<div class="@(anyEnabled ? "" : "d-none") card card-body coordinator-settings">
@if (Model.Active && anyEnabled)
{
<div class="position-absolute w-100 h-100 text-center rounded" id="blocker" style=" left: 0; top: 0; z-index: 1">
<h4 class="d-none pt-4">Settings cannot be changed while active</h4>
</div>
}
<div class="row">
<div class="col-sm-12 col-md-6">
<div class="form-check">
@@ -140,6 +160,11 @@
<input asp-for="BatchPayments" type="checkbox" class="form-check-input"/>
<p class="text-muted">Batch your pending payments (on-chain payouts awaiting payment) inside coinjoins.</p>
</div>
<div class="form-group form-check">
<label asp-for="ParanoidPayments" class="form-check-label">Paranoid payments</label>
<input asp-for="ParanoidPayments" type="checkbox" class="form-check-input"/>
<p class="text-muted">Only batch payments with fully private coins.</p>
</div>
<div class="form-group">
<label asp-for="CrossMixBetweenCoordinatorsMode" class="form-label">Mix funds between different coordinators</label>
<select asp-for="CrossMixBetweenCoordinatorsMode" class="form-select">

View File

@@ -324,32 +324,6 @@ public class WabisabiCoordinatorClientInstance:IHostedService
_logger.LogTrace(coinJoinStatusEventArgs.CoinJoinProgressEventArgs.GetType() + " :" +
e.Wallet.WalletName);
break;
case CompletedEventArgs completedEventArgs:
var result = completedEventArgs.CoinJoinResult;
if (completedEventArgs.CompletionStatus == CompletionStatus.Success && result is SuccessfulCoinJoinResult successfulCoinJoinResult)
{
Task.Run(async () =>
{
var wallet = (BTCPayWallet) e.Wallet;
await wallet.RegisterCoinjoinTransaction(successfulCoinJoinResult, CoordinatorName);
});
}
else if(result is DisruptedCoinJoinResult disruptedCoinJoinResult )
{
Task.Run(async () =>
{
// _logger.LogInformation("unlocking coins because round failed");
await _utxoLocker.TryUnlock(
disruptedCoinJoinResult.SignedCoins.Select(coin => coin.Outpoint).ToArray());
});
break;
}
_logger.LogTrace("Coinjoin complete! :" + e.Wallet.WalletName);
break;
case LoadedEventArgs loadedEventArgs:
stopWhenAllMixed = !((BTCPayWallet)loadedEventArgs.Wallet).BatchPayments;
_ = CoinJoinManager.StartAsync(loadedEventArgs.Wallet, stopWhenAllMixed, false, CancellationToken.None);

View File

@@ -7,6 +7,7 @@ using BTCPayServer.Abstractions.Services;
using BTCPayServer.Common;
using BTCPayServer.Services.Stores;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using NBitcoin;
@@ -60,7 +61,8 @@ public class WabisabiPlugin : BaseBTCPayServerPlugin
utxoLocker,
provider.GetRequiredService<EventAggregator>(),
provider.GetRequiredService<ILogger<WalletProvider>>(),
provider.GetRequiredService<BTCPayNetworkProvider>()
provider.GetRequiredService<BTCPayNetworkProvider>(),
provider.GetRequiredService<IMemoryCache>()
));
applicationBuilder.AddWabisabiCoordinator();
applicationBuilder.AddSingleton<IWalletProvider>(provider => provider.GetRequiredService<WalletProvider>());

View File

@@ -82,13 +82,6 @@ namespace BTCPayServer.Plugins.Wabisabi
}
}
if (wabisabiSettings.Settings.All(settings => !settings.Enabled))
{
await _storeRepository.UpdateSetting<WabisabiStoreSettings>(storeId, nameof(WabisabiStoreSettings), null!);
}
else
{
var res = await GetWabisabiForStore(storeId);
foreach (var wabisabiStoreCoordinatorSettings in wabisabiSettings.Settings)
{
@@ -114,7 +107,7 @@ namespace BTCPayServer.Plugins.Wabisabi
}
await _storeRepository.UpdateSetting(storeId, nameof(WabisabiStoreSettings), wabisabiSettings!);
}
_memoryCache.Remove(GetCacheKey(storeId));
await _walletProvider.SettingsUpdated(storeId, wabisabiSettings);
// var existingProcessor = (await _payoutProcessorService.GetProcessors(new PayoutProcessorService.PayoutProcessorQuery()

View File

@@ -57,6 +57,24 @@ namespace BTCPayServer.Plugins.Wabisabi
_socks5HttpClientHandler = serviceProvider.GetRequiredService<Socks5HttpClientHandler>();
}
[HttpGet("toggle-active")]
public async Task<IActionResult> ToggleActive(string storeId, string returnUrl)
{
var settings = await _WabisabiService.GetWabisabiForStore(storeId);
if (settings is null)
{
return NotFound();
}
settings.Active = !settings.Active;
await _WabisabiService.SetWabisabiForStore(storeId, settings);
TempData["SuccessMessage"] = $"Coinjoin is now {(settings.Active ? "active" : "inactive")}";
returnUrl = Url.EnsureLocal(returnUrl, HttpContext.Request);
if(returnUrl is null)
return RedirectToAction(nameof(UpdateWabisabiStoreSettings), new {storeId});
return Redirect(returnUrl);
}
[HttpGet("")]
public async Task<IActionResult> UpdateWabisabiStoreSettings(string storeId)
{

View File

@@ -1,6 +1,4 @@
using System.Collections.Generic;
using System.Linq;
using BTCPayServer.Client.JsonConverters;
using NBitcoin;
using Newtonsoft.Json;
@@ -9,7 +7,7 @@ namespace BTCPayServer.Plugins.Wabisabi;
public class WabisabiStoreSettings
{
public List<WabisabiStoreCoordinatorSettings> Settings { get; set; } = new();
public bool Active { get; set; } = true;
public string MixToOtherWallet { get; set; }
@@ -22,6 +20,7 @@ public class WabisabiStoreSettings
public int AnonymitySetTarget { get; set; } = 5;
public bool BatchPayments { get; set; } = true;
public bool ParanoidPayments { get; set; } = false;
public int ExtraJoinProbability { get; set; } = 0;
public CrossMixMode CrossMixBetweenCoordinatorsMode { get; set; } = CrossMixMode.WhenFree;
public int FeeRateMedianTimeFrameHours { get; set; }

View File

@@ -37,6 +37,7 @@ public class WalletProvider : PeriodicRunner,IWalletProvider
private readonly EventAggregator _eventAggregator;
private readonly ILogger<WalletProvider> _logger;
private readonly BTCPayNetworkProvider _networkProvider;
private readonly IMemoryCache _memoryCache;
public WalletProvider(
IServiceProvider serviceProvider,
@@ -46,7 +47,8 @@ public class WalletProvider : PeriodicRunner,IWalletProvider
IUTXOLocker utxoLocker,
EventAggregator eventAggregator,
ILogger<WalletProvider> logger,
BTCPayNetworkProvider networkProvider) : base(TimeSpan.FromMinutes(5))
BTCPayNetworkProvider networkProvider,
IMemoryCache memoryCache) : base(TimeSpan.FromMinutes(5))
{
UtxoLocker = utxoLocker;
_serviceProvider = serviceProvider;
@@ -56,6 +58,7 @@ public class WalletProvider : PeriodicRunner,IWalletProvider
_eventAggregator = eventAggregator;
_logger = logger;
_networkProvider = networkProvider;
_memoryCache = memoryCache;
}
public readonly ConcurrentDictionary<string, Lazy<Task<IWallet>>> LoadedWallets = new();
@@ -102,7 +105,7 @@ public class WalletProvider : PeriodicRunner,IWalletProvider
var accountKeyPath2 = await explorerClient.GetMetadataAsync<RootedKeyPath>(derivationStrategy,
WellknownMetadataKeys.AccountKeyPath);
accountKeyPath = accountKeyPath2 ?? accountKeyPath;
var smartifier = new Smartifier(_logger,_serviceProvider.GetRequiredService<WalletRepository>(),
var smartifier = new Smartifier(_memoryCache,_logger,_serviceProvider.GetRequiredService<WalletRepository>(),
explorerClient, derivationStrategy, name, UtxoLocker, accountKeyPath);
if (masterKey is null || accountKey is null || accountKeyPath is null)
{
@@ -113,7 +116,7 @@ public class WalletProvider : PeriodicRunner,IWalletProvider
}
else
{
var smartifier = new Smartifier(_logger,_serviceProvider.GetRequiredService<WalletRepository>(), explorerClient,
var smartifier = new Smartifier(_memoryCache, _logger,_serviceProvider.GetRequiredService<WalletRepository>(), explorerClient,
derivationStrategy, name, UtxoLocker, accountKeyPath);
keychain = new BTCPayKeyChain(explorerClient, derivationStrategy, null, null, smartifier);
}
@@ -242,7 +245,7 @@ public class WalletProvider : PeriodicRunner,IWalletProvider
public async Task SettingsUpdated(string storeId, WabisabiStoreSettings wabisabiSettings)
{
if (wabisabiSettings.Settings.All(settings => !settings.Enabled))
if (wabisabiSettings.Settings.All(settings => !settings.Enabled) || !wabisabiSettings.Active)
{
_cachedSettings?.Remove(storeId);
await UnloadWallet(storeId);