From 444ade7ac8cbefee80f64524046f9ef43dfc8a34 Mon Sep 17 00:00:00 2001 From: Kukks Date: Mon, 19 Feb 2024 10:45:22 +0100 Subject: [PATCH] improve ww --- .../BringinService.cs | 4 +- .../Components/BringinWidget.razor | 29 +-- .../BTCPayServer.Plugins.MicroNode.csproj | 2 - .../BTCPayCoinjoinCoinSelector.cs | 167 +++++++++++++----- .../BTCPayServer.Plugins.Wabisabi.csproj | 2 +- submodules/walletwasabi | 2 +- 6 files changed, 146 insertions(+), 60 deletions(-) diff --git a/Plugins/BTCPayServer.Plugins.Bringin/BringinService.cs b/Plugins/BTCPayServer.Plugins.Bringin/BringinService.cs index 1fcca5a..79856c3 100644 --- a/Plugins/BTCPayServer.Plugins.Bringin/BringinService.cs +++ b/Plugins/BTCPayServer.Plugins.Bringin/BringinService.cs @@ -469,9 +469,9 @@ public class BringinService : EventHostedServiceBase } } - public void ResetBalance(string storeId, PaymentMethodId pmi) + public async Task ResetBalance(string storeId, PaymentMethodId pmi) { - _ = HandleStoreAction(storeId, async bringinStoreSettings => + await HandleStoreAction(storeId, async bringinStoreSettings => { if (bringinStoreSettings.MethodSettings.TryGetValue(pmi.ToString(), out var methodSettings) && methodSettings.CurrentBalance > 0) { diff --git a/Plugins/BTCPayServer.Plugins.Bringin/Components/BringinWidget.razor b/Plugins/BTCPayServer.Plugins.Bringin/Components/BringinWidget.razor index 3b43a16..b5427e8 100644 --- a/Plugins/BTCPayServer.Plugins.Bringin/Components/BringinWidget.razor +++ b/Plugins/BTCPayServer.Plugins.Bringin/Components/BringinWidget.razor @@ -292,9 +292,19 @@ public string OnboardLink; - private void ResetBalance(PaymentMethodId pmi) + private async void ResetBalance(PaymentMethodId pmi) { - BringinService.ResetBalance(StoreId, pmi); + if (_saving) + return; + try + { + _saving = true; + await BringinService.ResetBalance(StoreId, pmi); + } + finally + { + _saving = false; + } } } @@ -475,15 +485,14 @@

@DisplayFormatter.Currency(method.Value.CurrentBalance, "BTC", DisplayFormatter.CurrencyFormat.None)

BTC (@DisplayFormatter.Currency(balanceInFiat.Value, "EUR", DisplayFormatter.CurrencyFormat.Code)) pending to forward once @DisplayFormatter.Currency(thresholdinBtc.Value, "BTC", DisplayFormatter.CurrencyFormat.Code) (@DisplayFormatter.Currency(method.Value.Threshold, "EUR")) is reached. - @if (method.Value.CurrentBalance > 0) - { - - //Clear balance - - } - + @if (method.Value.CurrentBalance > 0) + { + + //Clear balance + } + } - + @if (method.Value.PendingPayouts.Any()) {
diff --git a/Plugins/BTCPayServer.Plugins.MicroNode/BTCPayServer.Plugins.MicroNode.csproj b/Plugins/BTCPayServer.Plugins.MicroNode/BTCPayServer.Plugins.MicroNode.csproj index 72d71d3..22fbb41 100644 --- a/Plugins/BTCPayServer.Plugins.MicroNode/BTCPayServer.Plugins.MicroNode.csproj +++ b/Plugins/BTCPayServer.Plugins.MicroNode/BTCPayServer.Plugins.MicroNode.csproj @@ -37,8 +37,6 @@ - - diff --git a/Plugins/BTCPayServer.Plugins.Wabisabi/BTCPayCoinjoinCoinSelector.cs b/Plugins/BTCPayServer.Plugins.Wabisabi/BTCPayCoinjoinCoinSelector.cs index 51e15ee..857f4b9 100644 --- a/Plugins/BTCPayServer.Plugins.Wabisabi/BTCPayCoinjoinCoinSelector.cs +++ b/Plugins/BTCPayServer.Plugins.Wabisabi/BTCPayCoinjoinCoinSelector.cs @@ -2,20 +2,24 @@ using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; -using System.Text; using System.Threading.Tasks; -using Microsoft.Extensions.Logging; using NBitcoin; +using WalletWasabi.Blockchain.Analysis; +using WalletWasabi.Blockchain.Analysis.Clustering; using WalletWasabi.Blockchain.Keys; using WalletWasabi.Blockchain.TransactionOutputs; +using WalletWasabi.Blockchain.Transactions; using WalletWasabi.Crypto.Randomness; using WalletWasabi.Exceptions; using WalletWasabi.Extensions; using WalletWasabi.Helpers; +using WalletWasabi.Models; using WalletWasabi.WabiSabi; using WalletWasabi.WabiSabi.Backend.Rounds; using WalletWasabi.WabiSabi.Client; using WalletWasabi.WabiSabi.Client.StatusChangedEvents; +using WalletWasabi.WabiSabi.Models; +using WalletWasabi.WabiSabi.Models.MultipartyTransaction; using WalletWasabi.Wallets; namespace BTCPayServer.Plugins.Wabisabi; @@ -24,15 +28,25 @@ public class BTCPayCoinjoinCoinSelector : IRoundCoinSelector { private readonly BTCPayWallet _wallet; + private static BlockchainAnalyzer BlockchainAnalyzer { get; } = new(); public BTCPayCoinjoinCoinSelector(BTCPayWallet wallet) { _wallet = wallet; } - public async Task<(ImmutableList selected, Func, Task> acceptableSigned)> SelectCoinsAsync( - (IEnumerable Candidates, IEnumerable Ineligible) coinCandidates, - UtxoSelectionParameters utxoSelectionParameters, - Money liquidityClue, SecureRandom secureRandom) + public async + Task<( + ImmutableList selected, + Func, Task> acceptableRegistered, + Func< + ImmutableArray, + (IEnumerable outputTxOuts, Dictionary batchedPayments), + TransactionWithPrecomputedData, + RoundState, + Task> acceptableOutputs + )> + SelectCoinsAsync((IEnumerable Candidates, IEnumerable Ineligible) coinCandidates, + UtxoSelectionParameters utxoSelectionParameters, Money liquidityClue, SecureRandom secureRandom) { SmartCoin[] FilterCoinsMore(IEnumerable coins) { @@ -145,18 +159,114 @@ public class BTCPayCoinjoinCoinSelector : IRoundCoinSelector } _wallet.LogTrace(solution.ToString()); - return (solution.Coins.ToImmutableList(), async coins => + + async Task AcceptableRegistered(IEnumerable coins) { - var onlyForConsolidation = mixReasons.Length == 1 && mixReasons.Contains(IWallet.MixingReason.Consolidation); - if(onlyForConsolidation && coins.Count() < 10) + var remainingMixReasons = mixReasons.ToList(); + // var onlyForConsolidation = mixReasons.Length == 1 && mixReasons.Contains(IWallet.MixingReason.Consolidation); + if (mixReasons.Contains(IWallet.MixingReason.Consolidation) && coins.Count() < 10) { - _wallet.LogTrace("ShouldMix returned true only for consolidation, but less than 10 coins were registered successfully, so we will not mix"); - return false; + remainingMixReasons.Remove(IWallet.MixingReason.Consolidation); + // + // _wallet.LogTrace("ShouldMix returned true only for consolidation, but less than 10 coins were registered successfully, so we will not mix"); + // return false; } - return true; + if (mixReasons.Contains(IWallet.MixingReason.Payment) && !solution.HandledPayments.Any()) + { + if (!solution.HandledPayments.Any()) remainingMixReasons.Remove(IWallet.MixingReason.Payment); + // can the registered coins handle any of the solution payments? + // if not, then we should not proceed + // _wallet.LogTrace("ShouldMix returned true only for payments, but no handled payments were found, so we will not mix"); + //check if we can handle any of the payments in solution.HandledPayments with coins + + var effectiveSumOfRegisteredCoins = coins.Sum(coin => coin.EffectiveValue); + var canHandleAPayment = solution.HandledPayments.Any(payment => + { + var cost = payment.ToTxOut().EffectiveCost(utxoSelectionParameters.MiningFeeRate) + payment.Value; + return effectiveSumOfRegisteredCoins >= cost; + }); + if (!canHandleAPayment) + { + remainingMixReasons.Remove(IWallet.MixingReason.Payment); + } + } + + if (mixReasons.Contains(IWallet.MixingReason.NotPrivate) && coins.All(coin => coin.SmartCoin.IsPrivate(_wallet.AnonScoreTarget))) + { + remainingMixReasons.Remove(IWallet.MixingReason.NotPrivate); + } + + if (coins.Count() != solution.Coins.Count() && mixReasons.Contains(IWallet.MixingReason.ExtraJoin)) + { + remainingMixReasons.Remove(IWallet.MixingReason.ExtraJoin); + } + + if (remainingMixReasons.Count != mixReasons.Length) + { + _wallet.LogDebug($"Some mix reasons were removed due to the difference in registered coins: {string.Join(", ", mixReasons.Except(remainingMixReasons))}. Remaining: {string.Join(", ", remainingMixReasons)}"); + } + + return remainingMixReasons.Any(); + } + + async Task AcceptableOutputs(ImmutableArray registeredAliceClients, (IEnumerable outputTxOuts, Dictionary batchedPayments) outputTxOuts, TransactionWithPrecomputedData unsignedCoinJoin, RoundState roundState) + { + var remainingMixReasons = mixReasons.ToList(); + var ourCoins = registeredAliceClients.Select(client => client.SmartCoin); + var ourOutputsThatAreNotPayments = outputTxOuts.outputTxOuts.ToList(); + foreach (var batchedPayment in outputTxOuts.batchedPayments) + { + ourOutputsThatAreNotPayments.Remove(ourOutputsThatAreNotPayments.First(@out => @out.ScriptPubKey == batchedPayment.Key.ScriptPubKey && @out.Value == batchedPayment.Key.Value)); + } + + var smartTx = new SmartTransaction(unsignedCoinJoin.Transaction, new Height(HeightType.Unknown)); + foreach (var smartCoin in ourCoins) + { + smartTx.TryAddWalletInput(SmartCoin.Clone(smartCoin)); + } + + var outputCoins = new List(); + var matchedIndexes = new List(); + foreach (var txOut in ourOutputsThatAreNotPayments) + { + var index = unsignedCoinJoin.Transaction.Outputs.AsIndexedOutputs().First(@out => !matchedIndexes.Contains(@out.N) && @out.TxOut.ScriptPubKey == txOut.ScriptPubKey && @out.TxOut.Value == txOut.Value).N; + matchedIndexes.Add(index); + var coin = new SmartCoin(smartTx, index, new HdPubKey(new Key().PubKey, new KeyPath(0, 0, 0, 0, 0, 0), LabelsArray.Empty, KeyState.Clean)); + smartTx.TryAddWalletOutput(coin); + outputCoins.Add(coin); + } + + BlockchainAnalyzer.Analyze(smartTx); + var wavgInAnon = CoinjoinAnalyzer.WeightedAverage.Invoke(ourCoins.Select(coin => new CoinjoinAnalyzer.AmountWithAnonymity(coin.AnonymitySet, new Money(coin.Amount, MoneyUnit.BTC)))); + var wavgOutAnon = CoinjoinAnalyzer.WeightedAverage.Invoke(outputCoins.Select(coin => new CoinjoinAnalyzer.AmountWithAnonymity(coin.AnonymitySet, new Money(coin.Amount, MoneyUnit.BTC)))); + + + if (!outputTxOuts.batchedPayments.Any()) + { + remainingMixReasons.Remove(IWallet.MixingReason.Payment); + if (wavgOutAnon < wavgInAnon - CoinJoinCoinSelector.MaxWeightedAnonLoss) + { + remainingMixReasons.Remove(IWallet.MixingReason.NotPrivate); + } + } + else if (wavgOutAnon < wavgInAnon) + { + + remainingMixReasons.Remove(IWallet.MixingReason.NotPrivate); + remainingMixReasons.Remove(IWallet.MixingReason.ExtraJoin); + } + + if (remainingMixReasons.Contains(IWallet.MixingReason.Consolidation) && ourOutputsThatAreNotPayments.Count > registeredAliceClients.Length) + { + remainingMixReasons.Remove(IWallet.MixingReason.Consolidation); + } - } ); + + return remainingMixReasons.Any(); + } + + return (solution.Coins.ToImmutableList(), (IEnumerable coins) => AcceptableRegistered(coins) , AcceptableOutputs); } private async Task SelectCoinsInternal(UtxoSelectionParameters utxoSelectionParameters, @@ -180,37 +290,6 @@ public class BTCPayCoinjoinCoinSelector : IRoundCoinSelector solution.ConsolidationMode = consolidationMode; - // if (consolidationMode && coins.Count() < 8 && !coinjoiningOnlyForPayments && - // ineligibleCoins.Any(coin => !coin.Confirmed)) - // { - // //if we're in consolidation mode, and there are coins not eligible for a reason that will be ok in the near future, we should try to wait for them to become eligible instead of entering multiple coinjoins, which costs more. - // // why are they ineligible? banned if not too far in the future is ok, unconfrmed as well - // - // // if there are unconfirmed coins, we should wait for them to confirm, but since we cant determine if they will be unconfirmed for a long time,. let's play a random chance game: the more coins we have towards the 8 coin goal, the bigger the chance we proceed with the coinjoin - // var rand = Random.Shared.Next(1, 101); - // var chance = (coins.Count() / 8) * 100; - // _wallet.LogDebug( - // $"coin selection: consolidation mode, and there are coins not eligible for a reason that will be ok in the near future, we should try to wait for them to become eligible instead of entering multiple coinjoins, which costs more. random chance to proceed: {chance} > {rand} (random 0-100) continue: {chance > rand}"); - // if (chance > rand) - // { - // return solution; - // - // } - // } - // - // if (fullyPrivate && !coinjoiningOnlyForPayments && percentage ) - // { - // var rand = Random.Shared.Next(1, 1001); - // if (rand > _wallet.WabisabiStoreSettings.ExtraJoinProbability) - // { - // _wallet.LogTrace($"All coins are private and we have no pending payments. Skipping join."); - // return solution; - // } - // - // _wallet.LogTrace( - // "All coins are private and we have no pending payments but will join just to reduce timing analysis"); - // } - while (remainingCoins.Any()) { diff --git a/Plugins/BTCPayServer.Plugins.Wabisabi/BTCPayServer.Plugins.Wabisabi.csproj b/Plugins/BTCPayServer.Plugins.Wabisabi/BTCPayServer.Plugins.Wabisabi.csproj index 52957ef..c4914af 100644 --- a/Plugins/BTCPayServer.Plugins.Wabisabi/BTCPayServer.Plugins.Wabisabi.csproj +++ b/Plugins/BTCPayServer.Plugins.Wabisabi/BTCPayServer.Plugins.Wabisabi.csproj @@ -13,7 +13,7 @@ Coinjoin Allows you to integrate your btcpayserver store with coinjoins. - 1.0.71 + 1.0.72 true diff --git a/submodules/walletwasabi b/submodules/walletwasabi index f1feba6..32d6c82 160000 --- a/submodules/walletwasabi +++ b/submodules/walletwasabi @@ -1 +1 @@ -Subproject commit f1feba62af39a096f1b0f7c8b73179c029f42bff +Subproject commit 32d6c82f6e565071a2bdaaabdd2d81433dd5b198