Skip to content

Commit

Permalink
Introduce AbsoluteMininputCount coordinator config (WalletWasabi#13212)
Browse files Browse the repository at this point in the history
  • Loading branch information
Turbolay authored Jun 29, 2024
1 parent 569d20c commit 3656458
Show file tree
Hide file tree
Showing 14 changed files with 78 additions and 12 deletions.
4 changes: 4 additions & 0 deletions WalletWasabi.Daemon/Config.cs
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,9 @@ [ nameof(MaxCoordinationFeeRate)] = (
[ nameof(MaxCoinjoinMiningFeeRate)] = (
"Max mining fee rate in s/vb the client is willing to pay to participate into a round",
GetDecimalValue("MaxCoinjoinMiningFeeRate", PersistentConfig.MaxCoinJoinMiningFeeRate, cliArgs)),
[ nameof(AbsoluteMinInputCount)] = (
"Minimum number of inputs the client is willing to accept to participate into a round",
GetLongValue("AbsoluteMinInputCount", PersistentConfig.AbsoluteMinInputCount, cliArgs)),
};

// Check if any config value is overridden (either by an environment value, or by a CLI argument).
Expand Down Expand Up @@ -192,6 +195,7 @@ [ nameof(MaxCoinjoinMiningFeeRate)] = (
public string CoordinatorIdentifier => GetEffectiveValue<StringValue, string>(nameof(CoordinatorIdentifier));
public decimal MaxCoordinationFeeRate => GetEffectiveValue<DecimalValue, decimal>(nameof(MaxCoordinationFeeRate));
public decimal MaxCoinjoinMiningFeeRate => GetEffectiveValue<DecimalValue, decimal>(nameof(MaxCoinjoinMiningFeeRate));
public int AbsoluteMinInputCount => GetEffectiveValue<IntValue, int>(nameof(AbsoluteMinInputCount));
public ServiceConfiguration ServiceConfiguration { get; }

public static string DataDir { get; } = GetStringValue(
Expand Down
2 changes: 1 addition & 1 deletion WalletWasabi.Daemon/Global.cs
Original file line number Diff line number Diff line change
Expand Up @@ -400,7 +400,7 @@ private void RegisterCoinJoinComponents()
Tor.Http.IHttpClient roundStateUpdaterHttpClient = CoordinatorHttpClientFactory.NewHttpClient(Mode.SingleCircuitPerLifetime, RoundStateUpdaterCircuit);
HostedServices.Register<RoundStateUpdater>(() => new RoundStateUpdater(TimeSpan.FromSeconds(10), new WabiSabiHttpApiClient(roundStateUpdaterHttpClient)), "Round info updater");

var coinJoinConfiguration = new CoinJoinConfiguration(Config.CoordinatorIdentifier, Config.MaxCoordinationFeeRate, Config.MaxCoinjoinMiningFeeRate);
var coinJoinConfiguration = new CoinJoinConfiguration(Config.CoordinatorIdentifier, Config.MaxCoordinationFeeRate, Config.MaxCoinjoinMiningFeeRate, Config.AbsoluteMinInputCount);
HostedServices.Register<CoinJoinManager>(() => new CoinJoinManager(WalletManager, HostedServices.Get<RoundStateUpdater>(), CoordinatorHttpClientFactory, HostedServices.Get<WasabiSynchronizer>(), coinJoinConfiguration, CoinPrison), "CoinJoin Manager");
}

Expand Down
6 changes: 5 additions & 1 deletion WalletWasabi.Daemon/PersistentConfig.cs
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,9 @@ public record PersistentConfig : IConfigNg
[JsonPropertyName("MaxCoinJoinMiningFeeRate")]
public decimal MaxCoinJoinMiningFeeRate { get; init; } = Constants.DefaultMaxCoinJoinMiningFeeRate;

[JsonPropertyName("AbsoluteMinInputCount")]
public int AbsoluteMinInputCount { get; init; } = Constants.DefaultAbsoluteMinInputCount;

public bool DeepEquals(PersistentConfig other)
{
bool useTorIsEqual = Config.ObjectToTorMode(UseTor) == Config.ObjectToTorMode(other.UseTor);
Expand Down Expand Up @@ -152,7 +155,8 @@ public bool DeepEquals(PersistentConfig other)
EnableGpu == other.EnableGpu &&
CoordinatorIdentifier == other.CoordinatorIdentifier &&
MaxCoordinationFeeRate == other.MaxCoordinationFeeRate &&
MaxCoinJoinMiningFeeRate == other.MaxCoinJoinMiningFeeRate;
MaxCoinJoinMiningFeeRate == other.MaxCoinJoinMiningFeeRate &&
AbsoluteMinInputCount == other.AbsoluteMinInputCount;
}

public EndPoint GetBitcoinP2pEndPoint()
Expand Down
22 changes: 16 additions & 6 deletions WalletWasabi.Fluent/Models/UI/ApplicationSettings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,13 @@ public partial class ApplicationSettings : ReactiveObject
[AutoNotify] private string _localBitcoinCoreDataDir;
[AutoNotify] private bool _stopLocalBitcoinCoreOnShutdown;
[AutoNotify] private string _bitcoinP2PEndPoint;
[AutoNotify] private string _dustThreshold;

// Coordinator
[AutoNotify] private string _coordinatorUri;
[AutoNotify] private string _maxCoordinationFeeRate;
[AutoNotify] private string _maxCoinJoinMiningFeeRate;
[AutoNotify] private string _dustThreshold;
[AutoNotify] private string _absoluteMinInputCount;

// General
[AutoNotify] private bool _darkModeEnabled;
Expand Down Expand Up @@ -89,10 +92,13 @@ public ApplicationSettings(string persistentConfigFilePath, PersistentConfig per
_localBitcoinCoreDataDir = _startupConfig.LocalBitcoinCoreDataDir;
_stopLocalBitcoinCoreOnShutdown = _startupConfig.StopLocalBitcoinCoreOnShutdown;
_bitcoinP2PEndPoint = _startupConfig.GetBitcoinP2pEndPoint().ToString(defaultPort: -1);
_dustThreshold = _startupConfig.DustThreshold.ToString();

// Coordinator
_coordinatorUri = _startupConfig.GetCoordinatorUri();
_maxCoordinationFeeRate = _startupConfig.MaxCoordinationFeeRate.ToString(CultureInfo.InvariantCulture);
_maxCoinJoinMiningFeeRate = _startupConfig.MaxCoinJoinMiningFeeRate.ToString(CultureInfo.InvariantCulture);
_dustThreshold = _startupConfig.DustThreshold.ToString();
_absoluteMinInputCount = _startupConfig.AbsoluteMinInputCount.ToString(CultureInfo.InvariantCulture);

// General
_darkModeEnabled = _uiConfig.DarkModeEnabled;
Expand Down Expand Up @@ -138,10 +144,11 @@ public ApplicationSettings(string persistentConfigFilePath, PersistentConfig per
.Subscribe();

// Save on change - continuation. WhenAnyValue cannot have more than 12 arguments.
this.WhenAnyValue(x =>
x.MaxCoordinationFeeRate,
this.WhenAnyValue(
x => x.MaxCoordinationFeeRate,
x => x.MaxCoinJoinMiningFeeRate,
(_, _) => Unit.Default)
x => x.AbsoluteMinInputCount,
(_, _, _) => Unit.Default)
.Skip(1)
.ObserveOn(RxApp.MainThreadScheduler)
.Throttle(TimeSpan.FromMilliseconds(ThrottleTime))
Expand Down Expand Up @@ -281,7 +288,10 @@ private PersistentConfig ApplyChanges(PersistentConfig config)
Constants.DefaultMaxCoordinationFeeRate,
MaxCoinJoinMiningFeeRate = decimal.TryParse(MaxCoinJoinMiningFeeRate, out var maxCoinjoinMiningFeeRate) ?
maxCoinjoinMiningFeeRate :
Constants.DefaultMaxCoinJoinMiningFeeRate
Constants.DefaultMaxCoinJoinMiningFeeRate,
AbsoluteMinInputCount = int.TryParse(AbsoluteMinInputCount, out var absoluteMinInputCount) ?
absoluteMinInputCount :
Constants.DefaultAbsoluteMinInputCount
};
}
else
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ public partial class CoordinatorTabSettingsViewModel : RoutableViewModel
[AutoNotify] private string _coordinatorUri;
[AutoNotify] private string _maxCoordinationFeeRate;
[AutoNotify] private string _maxCoinJoinMiningFeeRate;
[AutoNotify] private string _absoluteMinInputCount;

public CoordinatorTabSettingsViewModel(IApplicationSettings settings)
{
Expand All @@ -32,11 +33,13 @@ public CoordinatorTabSettingsViewModel(IApplicationSettings settings)
this.ValidateProperty(x => x.CoordinatorUri, ValidateCoordinatorUri);
this.ValidateProperty(x => x.MaxCoordinationFeeRate, ValidateMaxCoordinationFeeRate);
this.ValidateProperty(x => x.MaxCoinJoinMiningFeeRate, ValidateMaxCoinJoinMiningFeeRate);
this.ValidateProperty(x => x.AbsoluteMinInputCount, ValidateAbsoluteMinInputCount);


_coordinatorUri = settings.CoordinatorUri;
_maxCoordinationFeeRate = settings.MaxCoordinationFeeRate;
_maxCoinJoinMiningFeeRate = settings.MaxCoinJoinMiningFeeRate;
_absoluteMinInputCount = settings.AbsoluteMinInputCount;

this.WhenAnyValue(x => x.Settings.CoordinatorUri)
.Subscribe(x => CoordinatorUri = x);
Expand Down Expand Up @@ -117,4 +120,28 @@ private void ValidateMaxCoinJoinMiningFeeRate(IValidationErrors errors)

Settings.MaxCoinJoinMiningFeeRate = maxCoinJoinMiningFeeRateDecimal.ToString(CultureInfo.InvariantCulture);
}

private void ValidateAbsoluteMinInputCount(IValidationErrors errors)
{
var absoluteMinInputCount = AbsoluteMinInputCount;

if (string.IsNullOrEmpty(absoluteMinInputCount))
{
return;
}

if (!int.TryParse(absoluteMinInputCount, out var absoluteMinInputCountInt))
{
errors.Add(ErrorSeverity.Error, "Invalid number.");
return;
}

if (absoluteMinInputCountInt < 2)
{
errors.Add(ErrorSeverity.Error, "Absolute min input count should be at least 2");
return;
}

Settings.AbsoluteMinInputCount = absoluteMinInputCountInt.ToString(CultureInfo.InvariantCulture);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ public partial class CoinJoinStateViewModel : ViewModelBase
private const string RandomlySkippedRoundMessage = "Skipping a round for better privacy";
private const string CoordinationFeeRateTooHighMessage = "Coordination fee rate was too high";
private const string CoinjoinMiningFeeRateTooHighMessage = "Mining fee rate was too high";
private const string MinInputCountTooLowMessage = "Min input count was too low";
private const string PauseMessage = "Coinjoin is paused";
private const string StoppedMessage = "Coinjoin has stopped";
private const string PressPlayToStartMessage = "Press Play to start";
Expand Down Expand Up @@ -386,6 +387,7 @@ private void ProcessStatusChange(StatusChangedEventArgs e)
CoinjoinError.RandomlySkippedRound => RandomlySkippedRoundMessage,
CoinjoinError.CoordinationFeeRateTooHigh => CoordinationFeeRateTooHighMessage,
CoinjoinError.MiningFeeRateTooHigh => CoinjoinMiningFeeRateTooHighMessage,
CoinjoinError.MinInputCountTooLow => MinInputCountTooLowMessage,
_ => GeneralErrorMessage
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,5 +30,9 @@
<CurrencyEntryBox Classes="standalone" Name="CoinJoinMiningFeeRateTextBox" Text="{Binding MaxCoinJoinMiningFeeRate}" CurrencyCode="s/vb"/>
</StackPanel>

<StackPanel ToolTip.Tip="The client will refuse to participate in rounds with a minimum input count lower than the value indicated here.">
<TextBlock Text="Min Input Count" />
<TextBox Classes="standalone" Name="AbsoluteMinInputCountTextBox" Text="{Binding AbsoluteMinInputCount}"/>
</StackPanel>
</StackPanel>
</UserControl>
2 changes: 1 addition & 1 deletion WalletWasabi.Tests/Helpers/WabiSabiFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -342,7 +342,7 @@ public static CoinJoinClient CreateTestCoinJoinClient(
outputProvider,
roundStateUpdater,
coinSelector,
new CoinJoinConfiguration("CoinJoinCoordinatorIdentifier", 0.3m, 150.0m),
new CoinJoinConfiguration("CoinJoinCoordinatorIdentifier", 0.3m, 150.0m, 21),
new LiquidityClueProvider(),
TimeSpan.Zero,
TimeSpan.Zero,
Expand Down
3 changes: 2 additions & 1 deletion WalletWasabi.Tests/UnitTests/Bases/ConfigManagerNgTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,8 @@ static string GetConfigString(string localBitcoinCoreDataDir)
"EnableGpu": true,
"CoordinatorIdentifier": "CoinJoinCoordinatorIdentifier",
"MaxCoordinationFeeRate": 0.0,
"MaxCoinJoinMiningFeeRate": 150.0
"MaxCoinJoinMiningFeeRate": 150.0,
"AbsoluteMinInputCount": 21
}
""";

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ public class NullApplicationSettings : IApplicationSettings
public string BitcoinP2PEndPoint { get; set; } = "";
public string MaxCoordinationFeeRate { get; set; } = "";
public string MaxCoinJoinMiningFeeRate { get; set; } = "";
public string AbsoluteMinInputCount { get; set; } = "";
public string CoordinatorUri { get; set; } = "";
public string BackendUri { get; set; } = "";
public string DustThreshold { get; set; } = "";
Expand Down
1 change: 1 addition & 0 deletions WalletWasabi/Helpers/Constants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ public static class Constants
public const decimal DefaultDustThreshold = 0.00005m;
public const decimal DefaultMaxCoordinationFeeRate = 0.0m;
public static readonly decimal DefaultMaxCoinJoinMiningFeeRate = 150.0m;
public static readonly int DefaultAbsoluteMinInputCount = 21;

public const string AlphaNumericCharacters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
public const string CapitalAlphaNumericCharacters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
Expand Down
13 changes: 12 additions & 1 deletion WalletWasabi/WabiSabi/Client/CoinJoin/Client/CoinJoinClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ public CoinJoinClient(
private OutputProvider OutputProvider { get; }
private RoundStateUpdater RoundStatusUpdater { get; }
private LiquidityClueProvider LiquidityClueProvider { get; }
public CoinJoinConfiguration CoinJoinConfiguration { get; }
private CoinJoinConfiguration CoinJoinConfiguration { get; }
private CoinJoinCoinSelector CoinJoinCoinSelector { get; }
private TimeSpan DoNotRegisterInLastMinuteTimeLimit { get; }
private TimeSpan FeeRateMedianTimeFrame { get; }
Expand Down Expand Up @@ -121,6 +121,11 @@ private async Task<RoundState> WaitForBlameRoundAsync(uint256 blameRoundId, Canc
throw new InvalidOperationException($"Blame Round ({roundState.Id}): Abandoning: the minimum output amount is too high.");
}

if (roundState.CoinjoinState.Parameters.MinInputCountByRound < CoinJoinConfiguration.AbsoluteMinInputCount)
{
throw new InvalidOperationException($"Blame Round ({roundState.Id}): Abandoning: the minimum input count was too low.");
}

if (!roundState.IsBlame && !IsRoundEconomic(roundState.CoinjoinState.Parameters.MiningFeeRate, RoundStatusUpdater.CoinJoinFeeRateMedians, FeeRateMedianTimeFrame))
{
throw new InvalidOperationException($"Blame Round ({roundState.Id}): Abandoning: the round is not economic.");
Expand Down Expand Up @@ -164,6 +169,12 @@ public async Task<CoinJoinResult> StartCoinJoinAsync(Func<Task<IEnumerable<Smart
currentRoundState.LogInfo(roundSkippedMessage);
throw new CoinJoinClientException(CoinjoinError.MiningFeeRateTooHigh, roundSkippedMessage);
}
if (roundParameters.MinInputCountByRound < CoinJoinConfiguration.AbsoluteMinInputCount)
{
string roundSkippedMessage = $"Min input count for the round was {roundParameters.MinInputCountByRound} but min allowed is {CoinJoinConfiguration.AbsoluteMinInputCount}.";
currentRoundState.LogInfo(roundSkippedMessage);
throw new CoinJoinClientException(CoinjoinError.MinInputCountTooLow, roundSkippedMessage);
}
if (SkipFactors.ShouldSkipRoundRandomly(SecureRandom, roundParameters.MiningFeeRate, RoundStatusUpdater.CoinJoinFeeRateMedians, currentRoundState.Id))
{
string roundSkippedMessage = "Round skipped randomly for better privacy.";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -783,4 +783,4 @@ private record CoinJoinClientStateHolder(CoinJoinClientState CoinJoinClientState
private record UiBlockedStateHolder(bool NeedRestart, bool StopWhenAllMixed, bool OverridePlebStop, IWallet OutputWallet);
}

public record CoinJoinConfiguration(string CoordinatorIdentifier, decimal MaxCoordinationFeeRate, decimal MaxCoinJoinMiningFeeRate);
public record CoinJoinConfiguration(string CoordinatorIdentifier, decimal MaxCoordinationFeeRate, decimal MaxCoinJoinMiningFeeRate, int AbsoluteMinInputCount);
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ public enum CoinjoinError
RandomlySkippedRound,
CoordinationFeeRateTooHigh,
MiningFeeRateTooHigh,
MinInputCountTooLow
}

public class StatusChangedEventArgs : EventArgs
Expand Down

0 comments on commit 3656458

Please sign in to comment.