ws update

This commit is contained in:
Kukks
2024-06-26 09:14:07 +02:00
parent df7c5e7cb7
commit 0969ebb909
11 changed files with 315 additions and 257 deletions

View File

@@ -2,6 +2,7 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.Immutable; using System.Collections.Immutable;
using System.Linq; using System.Linq;
using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using NBitcoin; using NBitcoin;
using WalletWasabi.Blockchain.Analysis; using WalletWasabi.Blockchain.Analysis;
@@ -17,6 +18,7 @@ using WalletWasabi.Models;
using WalletWasabi.WabiSabi; using WalletWasabi.WabiSabi;
using WalletWasabi.WabiSabi.Backend.Rounds; using WalletWasabi.WabiSabi.Backend.Rounds;
using WalletWasabi.WabiSabi.Client; using WalletWasabi.WabiSabi.Client;
using WalletWasabi.WabiSabi.Client.CoinJoin.Client;
using WalletWasabi.WabiSabi.Client.StatusChangedEvents; using WalletWasabi.WabiSabi.Client.StatusChangedEvents;
using WalletWasabi.WabiSabi.Models; using WalletWasabi.WabiSabi.Models;
using WalletWasabi.WabiSabi.Models.MultipartyTransaction; using WalletWasabi.WabiSabi.Models.MultipartyTransaction;
@@ -35,28 +37,24 @@ public class BTCPayCoinjoinCoinSelector : IRoundCoinSelector
} }
public async public async
Task<( Task<(ImmutableList<SmartCoin> selected, Func<IEnumerable<AliceClient>, Task<bool>> acceptableRegistered, Func<
ImmutableList<SmartCoin> selected, ImmutableArray<AliceClient>, (IEnumerable<TxOut> outputTxOuts, Dictionary<TxOut, PendingPayment>
Func<IEnumerable<AliceClient>, Task<bool>> acceptableRegistered, batchedPayments), TransactionWithPrecomputedData, RoundState, Task<bool>> acceptableOutputs)>
Func<
ImmutableArray<AliceClient>,
(IEnumerable<TxOut> outputTxOuts, Dictionary<TxOut, PendingPayment> batchedPayments),
TransactionWithPrecomputedData,
RoundState,
Task<bool>> acceptableOutputs
)>
SelectCoinsAsync((IEnumerable<SmartCoin> Candidates, IEnumerable<SmartCoin> Ineligible) coinCandidates, SelectCoinsAsync((IEnumerable<SmartCoin> Candidates, IEnumerable<SmartCoin> Ineligible) coinCandidates,
UtxoSelectionParameters utxoSelectionParameters, Money liquidityClue, SecureRandom secureRandom) RoundParameters roundParameters, Money liquidityClue, SecureRandom secureRandom, string coordinatorName)
{ {
SmartCoin[] FilterCoinsMore(IEnumerable<SmartCoin> coins) var log = new StringBuilder();
try
{
SmartCoin[] FilterCoinsMore(IEnumerable<SmartCoin> coins)
{ {
return coins return coins
.Where(coin => utxoSelectionParameters.AllowedInputScriptTypes.Contains(coin.ScriptType)) .Where(coin => roundParameters.AllowedInputTypes.Contains(coin.ScriptType))
.Where(coin => utxoSelectionParameters.AllowedInputAmounts.Contains(coin.Amount)) .Where(coin => roundParameters.AllowedInputAmounts.Contains(coin.Amount))
.Where(coin => .Where(coin =>
{ {
var effV = coin.EffectiveValue(utxoSelectionParameters.MiningFeeRate, var effV = coin.EffectiveValue(roundParameters.MiningFeeRate,
utxoSelectionParameters.CoordinationFeeRate); roundParameters.CoordinationFeeRate);
var percentageLeft = (effV.ToDecimal(MoneyUnit.BTC) / coin.Amount.ToDecimal(MoneyUnit.BTC)); var percentageLeft = (effV.ToDecimal(MoneyUnit.BTC) / coin.Amount.ToDecimal(MoneyUnit.BTC));
// filter out low value coins where 50% of the value would be eaten up by fees // filter out low value coins where 50% of the value would be eaten up by fees
return effV > Money.Zero && percentageLeft >= 0.5m; return effV > Money.Zero && percentageLeft >= 0.5m;
@@ -69,7 +67,7 @@ public class BTCPayCoinjoinCoinSelector : IRoundCoinSelector
{ {
return true; return true;
} }
if (!coin.HdPubKey.Labels.Contains("coinjoin") || coin.HdPubKey.Labels.Contains(utxoSelectionParameters.CoordinatorName)) if (!coin.HdPubKey.Labels.Contains("coinjoin") || coin.HdPubKey.Labels.Contains(coordinatorName))
{ {
return true; return true;
} }
@@ -78,7 +76,7 @@ public class BTCPayCoinjoinCoinSelector : IRoundCoinSelector
_wallet.WabisabiStoreSettings.CrossMixBetweenCoordinatorsMode == _wallet.WabisabiStoreSettings.CrossMixBetweenCoordinatorsMode ==
WabisabiStoreSettings.CrossMixMode.WhenFree) WabisabiStoreSettings.CrossMixMode.WhenFree)
{ {
return coin.Amount <= utxoSelectionParameters.CoordinationFeeRate.PlebsDontPayThreshold; return coin.Amount <= roundParameters.CoordinationFeeRate.PlebsDontPayThreshold;
} }
return false; return false;
@@ -93,7 +91,7 @@ public class BTCPayCoinjoinCoinSelector : IRoundCoinSelector
var payments = var payments =
(_wallet.BatchPayments (_wallet.BatchPayments
? await _wallet.DestinationProvider.GetPendingPaymentsAsync(utxoSelectionParameters) ? await _wallet.DestinationProvider.GetPendingPaymentsAsync(roundParameters)
: Array.Empty<PendingPayment>()).ToArray(); : Array.Empty<PendingPayment>()).ToArray();
var maxPerType = new Dictionary<AnonsetType, int>(); var maxPerType = new Dictionary<AnonsetType, int>();
@@ -113,7 +111,7 @@ public class BTCPayCoinjoinCoinSelector : IRoundCoinSelector
maxPerType.TryAdd(AnonsetType.Red, 1); maxPerType.TryAdd(AnonsetType.Red, 1);
} }
var isLowFee = utxoSelectionParameters.MiningFeeRate.SatoshiPerByte <= _wallet.LowFeeTarget; var isLowFee = roundParameters.MiningFeeRate.SatoshiPerByte <= _wallet.LowFeeTarget;
var consolidationMode = _wallet.ConsolidationMode switch var consolidationMode = _wallet.ConsolidationMode switch
{ {
ConsolidationModeType.Always => true, ConsolidationModeType.Always => true,
@@ -122,19 +120,19 @@ public class BTCPayCoinjoinCoinSelector : IRoundCoinSelector
ConsolidationModeType.WhenLowFeeAndManyUTXO => isLowFee && candidates.Count() > BTCPayWallet.HighAmountOfCoins, ConsolidationModeType.WhenLowFeeAndManyUTXO => isLowFee && candidates.Count() > BTCPayWallet.HighAmountOfCoins,
_ => throw new ArgumentOutOfRangeException() _ => throw new ArgumentOutOfRangeException()
}; };
var mixReasons = await _wallet.ShouldMix(utxoSelectionParameters.CoordinatorName, isLowFee, payments.Any()); var mixReasons = await _wallet.ShouldMix(coordinatorName, isLowFee, payments.Any());
if (!mixReasons.Any()) if (!mixReasons.Any())
{ {
throw new CoinJoinClientException(CoinjoinError.NoCoinsEligibleToMix, "ShouldMix returned false, so we will not mix"); throw new CoinJoinClientException(CoinjoinError.NoCoinsEligibleToMix, "ShouldMix returned false, so we will not mix");
} }
else else
{ {
_wallet.LogDebug($"ShouldMix returned true for {mixReasons.Length} reasons: {string.Join(", ", mixReasons)}"); log.AppendLine($"ShouldMix returned true for {mixReasons.Length} reasons: {string.Join(", ", mixReasons)}");
} }
Dictionary<AnonsetType, int> idealMinimumPerType = new Dictionary<AnonsetType, int>() Dictionary<AnonsetType, int> idealMinimumPerType = new Dictionary<AnonsetType, int>()
{{AnonsetType.Red, 1}, {AnonsetType.Orange, 1}, {AnonsetType.Green, 1}}; {{AnonsetType.Red, 1}, {AnonsetType.Orange, 1}, {AnonsetType.Green, 1}};
var solution = await SelectCoinsInternal(utxoSelectionParameters, candidates,payments, var solution = await SelectCoinsInternal(log,coordinatorName,roundParameters, candidates,payments,
Random.Shared.Next(20, 31), Random.Shared.Next(20, 31),
maxPerType, maxPerType,
idealMinimumPerType, idealMinimumPerType,
@@ -158,7 +156,7 @@ public class BTCPayCoinjoinCoinSelector : IRoundCoinSelector
throw new CoinJoinClientException(CoinjoinError.NoCoinsEligibleToMix, "ShouldMix returned true only for consolidation, but less than 10 coins were found, so we will not mix"); throw new CoinJoinClientException(CoinjoinError.NoCoinsEligibleToMix, "ShouldMix returned true only for consolidation, but less than 10 coins were found, so we will not mix");
} }
_wallet.LogTrace(solution.ToString()); log.AppendLine(solution.ToString());
async Task<bool> AcceptableRegistered(IEnumerable<AliceClient> coins) async Task<bool> AcceptableRegistered(IEnumerable<AliceClient> coins)
{ {
@@ -183,7 +181,7 @@ public class BTCPayCoinjoinCoinSelector : IRoundCoinSelector
var effectiveSumOfRegisteredCoins = coins.Sum(coin => coin.EffectiveValue); var effectiveSumOfRegisteredCoins = coins.Sum(coin => coin.EffectiveValue);
var canHandleAPayment = solution.HandledPayments.Any(payment => var canHandleAPayment = solution.HandledPayments.Any(payment =>
{ {
var cost = payment.ToTxOut().EffectiveCost(utxoSelectionParameters.MiningFeeRate) + payment.Value; var cost = payment.ToTxOut().EffectiveCost(roundParameters.MiningFeeRate) + payment.Value;
return effectiveSumOfRegisteredCoins >= cost; return effectiveSumOfRegisteredCoins >= cost;
}); });
if (!canHandleAPayment) if (!canHandleAPayment)
@@ -204,7 +202,7 @@ public class BTCPayCoinjoinCoinSelector : IRoundCoinSelector
if (remainingMixReasons.Count != mixReasons.Length) 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)}"); log.AppendLine($"Some mix reasons were removed due to the difference in registered coins: {string.Join(", ", mixReasons.Except(remainingMixReasons))}. Remaining: {string.Join(", ", remainingMixReasons)}");
} }
return remainingMixReasons.Any(); return remainingMixReasons.Any();
@@ -267,9 +265,20 @@ public class BTCPayCoinjoinCoinSelector : IRoundCoinSelector
} }
return (solution.Coins.ToImmutableList(), AcceptableRegistered , AcceptableOutputs); return (solution.Coins.ToImmutableList(), AcceptableRegistered , AcceptableOutputs);
}
finally
{
if(log.Length > 0)
_wallet.LogInfo(coordinatorName, $"coinselection: {log}");
}
} }
private async Task<SubsetSolution> SelectCoinsInternal(UtxoSelectionParameters utxoSelectionParameters, private async Task<SubsetSolution> SelectCoinsInternal(
StringBuilder log,
string coordinatorName,
RoundParameters utxoSelectionParameters,
IEnumerable<SmartCoin> coins, IEnumerable<SmartCoin> coins,
IEnumerable<PendingPayment> pendingPayments, IEnumerable<PendingPayment> pendingPayments,
int maxCoins, int maxCoins,
@@ -392,14 +401,14 @@ public class BTCPayCoinjoinCoinSelector : IRoundCoinSelector
if (chance <= rand) if (chance <= rand)
{ {
if (_wallet.MinimumDenominationAmount is not null && var minDenomAmount = Math.Min(_wallet.MinimumDenominationAmount ?? 0, _wallet.AllowedDenominations?.Any() is true? _wallet.AllowedDenominations.Min(): 0);
Money.Coins(solution.LeftoverValue).Satoshi < _wallet.MinimumDenominationAmount) if (minDenomAmount > 0 &&
Money.Coins(solution.LeftoverValue).Satoshi < minDenomAmount)
{ {
_wallet.LogDebug( log.AppendLine($"leftover value {solution.LeftoverValue} is less than minimum denomination amount {minDenomAmount} so we will try to add more coins");
$"coin selection: leftover value {solution.LeftoverValue} is less than minimum denomination amount {_wallet.MinimumDenominationAmount} so we will try to add more coins");
continue; continue;
} }
_wallet.LogDebug($"coin selection: no payments left but at {solution.Coins.Count()} coins. random chance to add another coin if: {chance} > {rand} (random 0-100) continue: {chance > rand}"); log.AppendLine($"no payments left but at {solution.Coins.Count()} coins. random chance to add another coin if: {chance} > {rand} (random 0-100) continue: {chance > rand}");
break; break;
} }
@@ -473,9 +482,9 @@ public enum AnonsetType
public class SubsetSolution public class SubsetSolution
{ {
private readonly UtxoSelectionParameters _utxoSelectionParameters; private readonly RoundParameters _utxoSelectionParameters;
public SubsetSolution(int totalPaymentsGross, IWallet wallet, UtxoSelectionParameters utxoSelectionParameters) public SubsetSolution(int totalPaymentsGross, IWallet wallet, RoundParameters utxoSelectionParameters)
{ {
_utxoSelectionParameters = utxoSelectionParameters; _utxoSelectionParameters = utxoSelectionParameters;
TotalPaymentsGross = totalPaymentsGross; TotalPaymentsGross = totalPaymentsGross;

View File

@@ -2,6 +2,8 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.Linq; using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using BTCPayServer.Client.Models; using BTCPayServer.Client.Models;
@@ -21,6 +23,7 @@ using NBXplorer.DerivationStrategy;
using NBXplorer.Models; using NBXplorer.Models;
using Newtonsoft.Json; using Newtonsoft.Json;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
using WabiSabi.Crypto.Randomness;
using WalletWasabi.Blockchain.Analysis; using WalletWasabi.Blockchain.Analysis;
using WalletWasabi.Blockchain.Analysis.Clustering; using WalletWasabi.Blockchain.Analysis.Clustering;
using WalletWasabi.Blockchain.Keys; using WalletWasabi.Blockchain.Keys;
@@ -69,6 +72,8 @@ public class BTCPayWallet : IWallet, IDestinationProvider
StoreRepository storeRepository, StoreRepository storeRepository,
IMemoryCache memoryCache) IMemoryCache memoryCache)
{ {
WalletId = new WalletWasabi.Wallets.WalletId(new Guid(SHA256.HashData(Encoding.UTF8.GetBytes(storeId)).Take(16)
.ToArray()));
KeyChain = keyChain; KeyChain = keyChain;
_walletRepository = walletRepository; _walletRepository = walletRepository;
_btcPayNetworkProvider = btcPayNetworkProvider; _btcPayNetworkProvider = btcPayNetworkProvider;
@@ -88,8 +93,8 @@ public class BTCPayWallet : IWallet, IDestinationProvider
} }
public string StoreId { get; set; } public string StoreId { get; set; }
public List<(DateTimeOffset time, Microsoft.Extensions.Logging.LogLevel level , string message)> LastLogs { get; private set; } = new(); public List<(DateTimeOffset time, Microsoft.Extensions.Logging.LogLevel level, string coordinator, string message)> LastLogs { get; private set; } = new();
public void Log(LogLevel logLevel, string logMessage, string callerFilePath = "", string callerMemberName = "", public void Log(LogLevel logLevel, string logMessage, string coordinator, string callerFilePath = "", string callerMemberName = "",
int callerLineNumber = -1) int callerLineNumber = -1)
{ {
var ll = logLevel switch var ll = logLevel switch
@@ -103,14 +108,20 @@ public class BTCPayWallet : IWallet, IDestinationProvider
_ => throw new ArgumentOutOfRangeException(nameof(logLevel)) _ => throw new ArgumentOutOfRangeException(nameof(logLevel))
}; };
if(LastLogs.FirstOrDefault().message != logMessage) if(LastLogs.FirstOrDefault().message != logMessage)
LastLogs.Insert(0, (DateTimeOffset.Now, ll, logMessage) ); LastLogs.Insert(0, (DateTimeOffset.Now, ll, coordinator, logMessage) );
if (LastLogs.Count >= 500) if (LastLogs.Count >= 500)
LastLogs.RemoveLast(); LastLogs.RemoveLast();
if (coordinator != null)
{
logMessage = $"[{coordinator}] {logMessage}";
}
_logger.Log(ll, logMessage, callerFilePath, callerMemberName, callerLineNumber); _logger.Log(ll, logMessage, callerFilePath, callerMemberName, callerLineNumber);
} }
public string WalletName => StoreId; public string WalletName => StoreId;
public WalletWasabi.Wallets.WalletId WalletId { get; }
public bool IsUnderPlebStop => !WabisabiStoreSettings.Active; public bool IsUnderPlebStop => !WabisabiStoreSettings.Active;
bool IWallet.IsMixable(string coordinator) bool IWallet.IsMixable(string coordinator)
@@ -122,6 +133,10 @@ public class BTCPayWallet : IWallet, IDestinationProvider
public IKeyChain KeyChain { get; } public IKeyChain KeyChain { get; }
public IDestinationProvider DestinationProvider => this; public IDestinationProvider DestinationProvider => this;
public OutputProvider GetOutputProvider(string coordinatorName)
{
return new OutputProvider(this, SecureRandom.Instance);
}
public int AnonScoreTarget => WabisabiStoreSettings.PlebMode? 5: WabisabiStoreSettings.AnonymitySetTarget; public int AnonScoreTarget => WabisabiStoreSettings.PlebMode? 5: WabisabiStoreSettings.AnonymitySetTarget;
public ConsolidationModeType ConsolidationMode => public ConsolidationModeType ConsolidationMode =>
@@ -141,6 +156,9 @@ public class BTCPayWallet : IWallet, IDestinationProvider
public bool BatchPayments => WabisabiStoreSettings.PlebMode || WabisabiStoreSettings.BatchPayments; public bool BatchPayments => WabisabiStoreSettings.PlebMode || WabisabiStoreSettings.BatchPayments;
public long? MinimumDenominationAmount => WabisabiStoreSettings.PlebMode? 10000 : WabisabiStoreSettings.MinimumDenominationAmount; public long? MinimumDenominationAmount => WabisabiStoreSettings.PlebMode? 10000 : WabisabiStoreSettings.MinimumDenominationAmount;
public long[]? AllowedDenominations =>
WabisabiStoreSettings.PlebMode ? null : WabisabiStoreSettings.AllowedDenominations;
public async Task<IWallet.MixingReason[]> ShouldMix(string coordinatorName, bool? isLowFee = null, public async Task<IWallet.MixingReason[]> ShouldMix(string coordinatorName, bool? isLowFee = null,
@@ -217,8 +235,8 @@ public class BTCPayWallet : IWallet, IDestinationProvider
public double GetPrivacyPercentage(CoinsView coins, int privateThreshold) public double GetPrivacyPercentage(CoinsView coins, int privateThreshold)
{ {
var privateAmount = coins.FilterBy(x => x.IsPrivate(this)).TotalAmount(); var privateAmount = new CoinsView(coins.Where(x => x.IsPrivate(this))).TotalAmount();
var normalAmount = coins.FilterBy(x => !x.IsPrivate(this)).TotalAmount(); var normalAmount = new CoinsView(coins.Where(x => !x.IsPrivate(this))).TotalAmount();
var privateDecimalAmount = privateAmount.ToDecimal(MoneyUnit.BTC); var privateDecimalAmount = privateAmount.ToDecimal(MoneyUnit.BTC);
var normalDecimalAmount = normalAmount.ToDecimal(MoneyUnit.BTC); var normalDecimalAmount = normalAmount.ToDecimal(MoneyUnit.BTC);
@@ -339,7 +357,7 @@ public class BTCPayWallet : IWallet, IDestinationProvider
} }
catch (Exception e) catch (Exception e)
{ {
this.LogError($"Could not compute coin candidate: {e.Message}"); this.LogError(coordinatorName,$"Could not compute coin candidate: {e.Message}");
return Array.Empty<SmartCoin>(); return Array.Empty<SmartCoin>();
} }
} }
@@ -543,7 +561,7 @@ public class BTCPayWallet : IWallet, IDestinationProvider
} }
catch (Exception e) catch (Exception e)
{ {
this.LogError($"Failed to analyze anonsets of tx {smartTx.GetHash()}"); this.LogError(coordinatorName,$"Failed to analyze anonsets of tx {smartTx.GetHash()}");
} }
@@ -649,7 +667,7 @@ public class BTCPayWallet : IWallet, IDestinationProvider
stopwatch.Stop(); stopwatch.Stop();
this.LogInfo($"Registered coinjoin result for {StoreId} in {stopwatch.Elapsed}"); this.LogInfo(coordinatorName,$"Registered coinjoin result for {StoreId} in {stopwatch.Elapsed}");
_memoryCache.Remove(WabisabiService.GetCacheKey(StoreId) + "cjhistory"); _memoryCache.Remove(WabisabiService.GetCacheKey(StoreId) + "cjhistory");
break; break;
@@ -657,7 +675,7 @@ public class BTCPayWallet : IWallet, IDestinationProvider
catch (Exception e) catch (Exception e)
{ {
this.LogError( "Could not save coinjoin progress! " + e.Message); this.LogError(coordinatorName,"Could not save coinjoin progress! " + e.Message);
// ignored // ignored
} }
@@ -691,7 +709,7 @@ public async Task<IEnumerable<IDestination>> GetNextDestinationsAsync(int count,
_btcPayWallet.ReserveAddressAsync(StoreId ,DerivationScheme, "coinjoin"))).ContinueWith(task => task.Result.Select(information => information.Address)); _btcPayWallet.ReserveAddressAsync(StoreId ,DerivationScheme, "coinjoin"))).ContinueWith(task => task.Result.Select(information => information.Address));
} }
public async Task<IEnumerable<PendingPayment>> GetPendingPaymentsAsync( UtxoSelectionParameters roundParameters) public async Task<IEnumerable<PendingPayment>> GetPendingPaymentsAsync(RoundParameters roundParameters)
{ {
@@ -715,11 +733,6 @@ public async Task<IEnumerable<IDestination>> GetNextDestinationsAsync(int count,
var payoutBlob = data.GetBlob(_btcPayNetworkJsonSerializerSettings); var payoutBlob = data.GetBlob(_btcPayNetworkJsonSerializerSettings);
var value = new Money(payoutBlob.CryptoAmount.Value, MoneyUnit.BTC); var value = new Money(payoutBlob.CryptoAmount.Value, MoneyUnit.BTC);
if (!roundParameters.AllowedOutputAmounts.Contains(value) ||
!roundParameters.AllowedOutputScriptTypes.Contains(bitcoinLikeClaimDestination.Address.ScriptPubKey.GetScriptType()))
{
return null;
}
return new PendingPayment() return new PendingPayment()
{ {
Identifier = data.Id, Identifier = data.Id,
@@ -738,9 +751,9 @@ public async Task<IEnumerable<IDestination>> GetNextDestinationsAsync(int count,
} }
} }
public Task<ScriptType> GetScriptTypeAsync() public Task<ScriptType[]> GetScriptTypeAsync()
{ {
return Task.FromResult(DerivationScheme.GetDerivation(0).ScriptPubKey.GetScriptType()); return Task.FromResult(new []{DerivationScheme.GetDerivation(0).ScriptPubKey.GetScriptType()});
} }
private Action<(uint256 roundId, uint256 transactionId, int outputIndex)> PaymentSucceeded(string payoutId) private Action<(uint256 roundId, uint256 transactionId, int outputIndex)> PaymentSucceeded(string payoutId)

View File

@@ -33,6 +33,7 @@ using WalletWasabi.WabiSabi;
using WalletWasabi.WabiSabi.Backend; using WalletWasabi.WabiSabi.Backend;
using WalletWasabi.WabiSabi.Backend.Rounds.CoinJoinStorage; using WalletWasabi.WabiSabi.Backend.Rounds.CoinJoinStorage;
using WalletWasabi.WabiSabi.Backend.Statistics; using WalletWasabi.WabiSabi.Backend.Statistics;
using WalletWasabi.WabiSabi.Client;
using WalletWasabi.WabiSabi.Models; using WalletWasabi.WabiSabi.Models;
using WalletWasabi.WebClients.Wasabi; using WalletWasabi.WebClients.Wasabi;
@@ -266,7 +267,14 @@ public class WabisabiCoordinatorService : PeriodicRunner
{ {
instance.WasabiCoordinatorStatusFetcher.OverrideConnected = null; instance.WasabiCoordinatorStatusFetcher.OverrideConnected = null;
} }
_instanceManager.AddCoordinator("Local Coordinator", "local", _ => null, cachedSettings.TermsConditions, cachedSettings.CoordinatorDescription);
var coinjoinConfig = new CoinJoinConfiguration(WabiSabiCoordinator.Config.CoordinatorIdentifier, 10m, 100m);
_instanceManager.AddCoordinator("Local Coordinator",
"local", _ => null,
cachedSettings.TermsConditions,
cachedSettings.CoordinatorDescription,
coinjoinConfig
);
} }
public async Task StopAsync(CancellationToken cancellationToken) public async Task StopAsync(CancellationToken cancellationToken)

View File

@@ -49,4 +49,5 @@ public class DiscoveredCoordinator
public string Name { get; set; } public string Name { get; set; }
public string Relay { get; set; } public string Relay { get; set; }
public string Description { get; set; } public string Description { get; set; }
public string? CoinjoinIdentifier { get; set; }
} }

View File

@@ -371,7 +371,7 @@ updateInProgressAnimation(myChart);
RoundState currentRound = null; RoundState currentRound = null;
CoinJoinTracker tracker = null; CoinJoinTracker tracker = null;
if (coordinator.CoinJoinManager.TrackedCoinJoins?.TryGetValue(wallet.WalletName, out tracker) is true && if (coordinator.CoinJoinManager.TrackedCoinJoins?.TryGetValue(wallet.WalletId, out tracker) is true &&
tracker?.CoinJoinClient?.CurrentRoundId is { } && tracker?.CoinJoinClient?.CurrentRoundId is { } &&
tracker?.CoinJoinClient?.RoundStatusUpdater?.RoundStates?.TryGetValue(tracker?.CoinJoinClient?.CurrentRoundId, out currentRound) is true) tracker?.CoinJoinClient?.RoundStatusUpdater?.RoundStates?.TryGetValue(tracker?.CoinJoinClient?.CurrentRoundId, out currentRound) is true)
{ {
@@ -397,7 +397,7 @@ updateInProgressAnimation(myChart);
else if (coordinator.WasabiCoordinatorStatusFetcher.Connected) else if (coordinator.WasabiCoordinatorStatusFetcher.Connected)
{ {
var roundParameters = coordinator.RoundStateUpdater.RoundStates.LastOrDefault(pair => pair.Value.BlameOf == uint256.Zero).Value?.CoinjoinState.Parameters; var roundParameters = coordinator.RoundStateUpdater.RoundStates.LastOrDefault(pair => pair.Value.BlameOf == uint256.Zero).Value?.CoinjoinState.Parameters;
coordinator.CoinJoinManager.TrackedWallets.TryGetValue(wallet.WalletName, out var trackedWallet); coordinator.CoinJoinManager.TrackedWallets.TryGetValue(wallet.WalletId, out var trackedWallet);
if(trackedWallet is {} && setting.RoundWhenEnabled is not null && roundParameters is not null && !BTCPayWallet.IsRoundOk(roundParameters, setting)) if(trackedWallet is {} && setting.RoundWhenEnabled is not null && roundParameters is not null && !BTCPayWallet.IsRoundOk(roundParameters, setting))
{ {
<a asp-controller="WabisabiStore" asp-action="UpdateWabisabiStoreSettings" asp-route-storeId="@storeId" class="h6 text-danger"> <a asp-controller="WabisabiStore" asp-action="UpdateWabisabiStoreSettings" asp-route-storeId="@storeId" class="h6 text-danger">
@@ -416,7 +416,7 @@ updateInProgressAnimation(myChart);
@{ @{
if (coordinator.CoinPrison is not null) 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, out _));
@if (bannedCoins.Any()) @if (bannedCoins.Any())
{ {
<div class="text-muted">@bannedCoins.Count() banned coins(for disrupting rounds)</div> <div class="text-muted">@bannedCoins.Count() banned coins(for disrupting rounds)</div>
@@ -520,6 +520,7 @@ updateInProgressAnimation(myChart);
<thead> <thead>
<tr> <tr>
<th>Time</th> <th>Time</th>
<th>Coordinator</th>
<th>Message</th> <th>Message</th>
</tr> </tr>
</thead> </thead>
@@ -531,6 +532,9 @@ updateInProgressAnimation(myChart);
<td> <td>
<small class="text-muted" data-timeago-unixms="@evt.time.ToUnixTimeMilliseconds()">@evt.time.ToTimeAgo()</small> <small class="text-muted" data-timeago-unixms="@evt.time.ToUnixTimeMilliseconds()">@evt.time.ToTimeAgo()</small>
</td> </td>
<td>
<pre>@evt.coordinator</pre>
</td>
<td> <td>
<pre>@evt.message</pre> <pre>@evt.message</pre>
</td> </td>

View File

@@ -8,6 +8,7 @@
@using BTCPayServer.Services.Stores @using BTCPayServer.Services.Stores
@using Microsoft.AspNetCore.Mvc.TagHelpers @using Microsoft.AspNetCore.Mvc.TagHelpers
@using WalletWasabi.Backend.Controllers @using WalletWasabi.Backend.Controllers
@using WalletWasabi.Blockchain.Analysis
@using WalletWasabi.Wallets @using WalletWasabi.Wallets
@model BTCPayServer.Plugins.Wabisabi.WabisabiStoreSettings @model BTCPayServer.Plugins.Wabisabi.WabisabiStoreSettings
@inject WabisabiCoordinatorClientInstanceManager WabisabiCoordinatorClientInstanceManager @inject WabisabiCoordinatorClientInstanceManager WabisabiCoordinatorClientInstanceManager
@@ -96,14 +97,14 @@
</style> </style>
<div class="@(anyEnabled ? "" : "d-none") card card-body coordinator-settings"> <div class="@(anyEnabled ? "" : "d-none") card card-body coordinator-settings">
@if (Model.Active && anyEnabled) @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"> <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> <h4 class="d-none pt-4">Settings cannot be changed while active</h4>
</div> </div>
} }
<div class="row"> <div class="row">
<div class="col-sm-12 col-md-6"> <div class="col-sm-12 col-md-6">
<div class="form-check"> <div class="form-check">
<input class="form-check-input plebModeRadio" <input class="form-check-input plebModeRadio"
@@ -123,8 +124,8 @@
<p class="text-muted">The world is broken and I need to be vigilant about my bitcoin practices.</p> <p class="text-muted">The world is broken and I need to be vigilant about my bitcoin practices.</p>
</div> </div>
</div> </div>
</div> </div>
<div id="advanced" class="@(Model.PlebMode ? "d-none" : "")"> <div id="advanced" class="@(Model.PlebMode ? "d-none" : "")">
<button type="submit" name="command" value="reset" class="btn btn-link">Reset to defaults </button> <button type="submit" name="command" value="reset" class="btn btn-link">Reset to defaults </button>
<div class="row"> <div class="row">
@@ -201,12 +202,32 @@
</div> </div>
</div> </div>
</div> </div>
<div class="row">
<div class="col-sm-12 col-md-6">
<div class="form-group"> <div class="form-group">
<label asp-for="MinimumDenominationAmount" class="form-label">Minimum denomination (in sats)</label> <label asp-for="MinimumDenominationAmount" class="form-label">Minimum denomination (in sats)</label>
<input type="number" class="form-control" asp-for="MinimumDenominationAmount" placeholder="sats" min="0"> <input type="number" class="form-control" asp-for="MinimumDenominationAmount" placeholder="sats" min="0">
<p class="text-muted">Do no use any of the standard denominations below this amount (creates change (which will get remixed) but prevent tiny utxos)</p> <p class="text-muted">Do no use any of the standard denominations below this amount (creates change (which will get remixed) but prevent tiny utxos)</p>
</div> </div>
</div>
<div class="col-sm-12 col-md-6">
<div class="form-group">
<label asp-for="AllowedDenominations" class="form-label">Allowed denominations (in sats)</label>
<select asp-for="AllowedDenominations" class="form-select" multiple="multiple">
@foreach (var denomination in BlockchainAnalyzer.StdDenoms)
{
<option value="@denomination">@denomination sats</option>
}
</select>
<p>DO NOT USE UNLESS YOU KNOW WHAT YOU ARE DOING</p>
<p class="text-muted">Only generate outputs of these denoms. Leave blank to ignore. Generates change. You must match other user's settings to gain any anonymity.</p>
</div>
</div>
</div>
<div class="form-group form-check"> <div class="form-group form-check">
<label asp-for="RedCoinIsolation" class="form-check-label">Cautious coinjoin entry mode </label> <label asp-for="RedCoinIsolation" class="form-check-label">Cautious coinjoin entry mode </label>
<input asp-for="RedCoinIsolation" type="checkbox" class="form-check-input"/> <input asp-for="RedCoinIsolation" type="checkbox" class="form-check-input"/>
@@ -307,7 +328,7 @@
</div> </div>
</div> </div>
</div> </div>
@for (var index = 0; index < Model.Settings.Count; index++) @for (var index = 0; index < Model.Settings.Count; index++)

View File

@@ -104,7 +104,7 @@ public class WabisabiCoordinatorClientInstanceManager:IHostedService
public void AddCoordinator(string displayName, string name, public void AddCoordinator(string displayName, string name,
Func<IServiceProvider, Uri> fetcher, string termsConditions = null, string description = null) Func<IServiceProvider, Uri> fetcher, string termsConditions = null, string description = null, CoinJoinConfiguration configuration = null)
{ {
if (termsConditions is null && name == "zksnacks") if (termsConditions is null && name == "zksnacks")
{ {
@@ -171,7 +171,7 @@ public class WabisabiCoordinatorClientInstanceManager:IHostedService
var instance = new WabisabiCoordinatorClientInstance( var instance = new WabisabiCoordinatorClientInstance(
displayName, displayName,
name, url is null? null: new Uri(url), wasabiHttpClientFactory,_provider.GetService<ILoggerFactory>(), _provider, UTXOLocker, name, url is null? null: new Uri(url), wasabiHttpClientFactory,_provider.GetService<ILoggerFactory>(), _provider, UTXOLocker,
_provider.GetService<WalletProvider>(), termsConditions, description); _provider.GetService<WalletProvider>(), termsConditions, description, configuration);
if (HostedServices.TryAdd(instance.CoordinatorName, instance)) if (HostedServices.TryAdd(instance.CoordinatorName, instance))
{ {
if(started) if(started)
@@ -299,8 +299,7 @@ public class WabisabiCoordinatorClientInstance:IHostedService
ILoggerFactory loggerFactory, ILoggerFactory loggerFactory,
IServiceProvider serviceProvider, IServiceProvider serviceProvider,
IUTXOLocker utxoLocker, IUTXOLocker utxoLocker,
WalletProvider walletProvider, string termsConditions, string description, string coordinatorIdentifier = "CoinJoinCoordinatorIdentifier" WalletProvider walletProvider, string termsConditions, string description, CoinJoinConfiguration config)
)
{ {
_utxoLocker = utxoLocker; _utxoLocker = utxoLocker;
@@ -348,7 +347,7 @@ public class WabisabiCoordinatorClientInstance:IHostedService
CoinJoinManager = new CoinJoinManager(coordinatorName, WalletProvider, RoundStateUpdater, CoinJoinManager = new CoinJoinManager(coordinatorName, WalletProvider, RoundStateUpdater,
WasabiHttpClientFactory, WasabiHttpClientFactory,
WasabiCoordinatorStatusFetcher, coordinatorIdentifier, CoinPrison); WasabiCoordinatorStatusFetcher, config, CoinPrison);
CoinJoinManager.StatusChanged += OnStatusChanged; CoinJoinManager.StatusChanged += OnStatusChanged;
_hostedServices.Register<RoundStateUpdater>(() => RoundStateUpdater, "RoundStateUpdater"); _hostedServices.Register<RoundStateUpdater>(() => RoundStateUpdater, "RoundStateUpdater");

View File

@@ -27,6 +27,7 @@ using WalletWasabi.Backend.Controllers;
using WalletWasabi.Blockchain.TransactionBuilding; using WalletWasabi.Blockchain.TransactionBuilding;
using WalletWasabi.Blockchain.TransactionOutputs; using WalletWasabi.Blockchain.TransactionOutputs;
using WalletWasabi.Extensions; using WalletWasabi.Extensions;
using WalletWasabi.WabiSabi.Client;
namespace BTCPayServer.Plugins.Wabisabi namespace BTCPayServer.Plugins.Wabisabi
{ {
@@ -244,7 +245,8 @@ namespace BTCPayServer.Plugins.Wabisabi
{ {
coordSettings.DiscoveredCoordinators.Add(viewModel); coordSettings.DiscoveredCoordinators.Add(viewModel);
await _wabisabiCoordinatorService.UpdateSettings(coordSettings); await _wabisabiCoordinatorService.UpdateSettings(coordSettings);
_instanceManager.AddCoordinator(viewModel.Name, viewModel.Name, provider => viewModel.Uri, viewModel.Description); var config = new CoinJoinConfiguration();
_instanceManager.AddCoordinator(viewModel.Name, viewModel.Name, provider => viewModel.Uri, null,viewModel.Description, config);
TempData["SuccessMessage"] = $"Coordinator {viewModel.Name } added and started"; TempData["SuccessMessage"] = $"Coordinator {viewModel.Name } added and started";
} }

View File

@@ -31,6 +31,7 @@ public class WabisabiStoreSettings
public CrossMixMode CrossMixBetweenCoordinatorsMode { get; set; } = CrossMixMode.WhenFree; public CrossMixMode CrossMixBetweenCoordinatorsMode { get; set; } = CrossMixMode.WhenFree;
public int FeeRateMedianTimeFrameHours { get; set; } public int FeeRateMedianTimeFrameHours { get; set; }
public long MinimumDenominationAmount { get; set; } = 10000; public long MinimumDenominationAmount { get; set; } = 10000;
public long[] AllowedDenominations { get; set; }
public int ExplicitHighestFeeTarget { get; set; } = BTCPayWallet.DefaultExplicitHighestFeeTarget; public int ExplicitHighestFeeTarget { get; set; } = BTCPayWallet.DefaultExplicitHighestFeeTarget;
public int LowFeeTarget { get; set; } = BTCPayWallet.DefaultLowFeeTarget; public int LowFeeTarget { get; set; } = BTCPayWallet.DefaultLowFeeTarget;

View File

@@ -28,7 +28,7 @@ namespace BTCPayServer.Plugins.Wabisabi;
public class WalletProvider : PeriodicRunner,IWalletProvider public class WalletProvider : PeriodicRunner,IWalletProvider
{ {
private Dictionary<string, WabisabiStoreSettings>? _cachedSettings; private ConcurrentDictionary<string, WabisabiStoreSettings>? _cachedSettings;
private readonly IServiceProvider _serviceProvider; private readonly IServiceProvider _serviceProvider;
private readonly StoreRepository _storeRepository; private readonly StoreRepository _storeRepository;
private readonly IExplorerClientProvider _explorerClientProvider; private readonly IExplorerClientProvider _explorerClientProvider;
@@ -255,8 +255,8 @@ public class WalletProvider : PeriodicRunner,IWalletProvider
{ {
Task.Run(async () => Task.Run(async () =>
{ {
_cachedSettings = _cachedSettings = new ConcurrentDictionary<string, WabisabiStoreSettings>(
await _storeRepository.GetSettingsAsync<WabisabiStoreSettings>(nameof(WabisabiStoreSettings)); (await _storeRepository.GetSettingsAsync<WabisabiStoreSettings>(nameof(WabisabiStoreSettings))));
_initialLoad.SetResult(); _initialLoad.SetResult();
}, cancellationToken); }, cancellationToken);
_disposables.Add(_eventAggregator.SubscribeAsync<StoreRemovedEvent>(async @event => _disposables.Add(_eventAggregator.SubscribeAsync<StoreRemovedEvent>(async @event =>