Refactoring musicbox
MODIFY
WalletWasabi.Fluent/ViewModels/Wallets/CoinJoinStateViewModel.cs
WalletWasabi.Fluent/ViewModels/Wallets/CoinJoinStateViewModel.cs
@@ -25,6 +25,7 @@ public partial class CoinJoinStateViewModel : ViewModelBase
private readonly StateMachine<State, Trigger> _stateMachine;
private readonly Wallet _wallet;
private readonly DispatcherTimer _countdownTimer;
+ private readonly DispatcherTimer _autoCoinJoinStartTimer;
private readonly MusicStatusMessageViewModel _countDownMessage = new() { Message = "Waiting to auto-start coinjoin" };
private readonly MusicStatusMessageViewModel _waitingMessage = new() { Message = "Waiting for coinjoin" };
@@ -60,12 +61,14 @@ public partial class CoinJoinStateViewModel : ViewModelBase
private DateTimeOffset _countDownStartTime;
private DateTimeOffset _countDownEndTime;
- private bool _overridePlebStop;
private CoinJoinProgressEventArgs? _lastStatusMessage;
+ public bool IsAutoCoinJoinEnabled => WalletVm.Settings.AutoCoinJoin;
+
public CoinJoinStateViewModel(WalletViewModel walletVm, IObservable<Unit> balanceChanged)
{
_wallet = walletVm.Wallet;
+ WalletVm = walletVm;
_elapsedTime = "";
_remainingTime = "";
@@ -85,8 +88,8 @@ public partial class CoinJoinStateViewModel : ViewModelBase
.Subscribe(text => IsBalanceDisplayed = text.Contains("BTC"));
var initialState = walletVm.Settings.AutoCoinJoin
- ? State.AutoCoinJoin
- : State.ManualCoinJoin;
+ ? State.WaitingForAutoStart
+ : State.StopOrPause;
if (walletVm.Wallet.KeyManager.IsHardwareWallet || walletVm.Wallet.KeyManager.IsWatchOnly)
{
@@ -95,7 +98,7 @@ public partial class CoinJoinStateViewModel : ViewModelBase
_stateMachine = new StateMachine<State, Trigger>(initialState);
- ConfigureStateMachine(coinJoinManager);
+ ConfigureStateMachine();
balanceChanged.Subscribe(_ => _stateMachine.Fire(Trigger.BalanceChanged));
@@ -108,16 +111,28 @@ public partial class CoinJoinStateViewModel : ViewModelBase
if (_wallet.KeyManager.IsCoinjoinProfileSelected)
{
- _stateMachine.Fire(Trigger.Play);
+ var overridePlebStop = _stateMachine.IsInState(State.PlebStopActive);
+ await coinJoinManager.StartAsync(_wallet, stopWhenAllMixed: !IsAutoCoinJoinEnabled, overridePlebStop, CancellationToken.None);
}
});
- PauseCommand = ReactiveCommand.Create(() => _stateMachine.Fire(Trigger.Pause));
-
- StopCommand = ReactiveCommand.Create(() => _stateMachine.Fire(Trigger.Stop));
+ StopPauseCommand = ReactiveCommand.CreateFromTask(async () =>
+ {
+ await coinJoinManager.StopAsync(_wallet, CancellationToken.None);
+ });
walletVm.Settings.WhenAnyValue(x => x.AutoCoinJoin)
- .Subscribe(SetAutoCoinJoin);
+ .SubscribeAsync(async (autoCoinJoin) =>
+ {
+ if (autoCoinJoin)
+ {
+ await coinJoinManager.StartAsync(_wallet, stopWhenAllMixed: false, false, CancellationToken.None);
+ }
+ else
+ {
+ await coinJoinManager.StopAsync(_wallet, CancellationToken.None);
+ }
+ });
walletVm.Settings.WhenAnyValue(x => x.PlebStopThreshold)
.SubscribeAsync(async _ =>
@@ -128,6 +143,13 @@ public partial class CoinJoinStateViewModel : ViewModelBase
_stateMachine.Fire(Trigger.PlebStopChanged);
});
+ _autoCoinJoinStartTimer = new DispatcherTimer { Interval = TimeSpan.FromMinutes(Random.Shared.Next(5, 16)) };
+ _autoCoinJoinStartTimer.Tick += async (_, _) =>
+ {
+ await coinJoinManager.StartAsync(_wallet, stopWhenAllMixed: false, false, CancellationToken.None);
+ _autoCoinJoinStartTimer.Stop();
+ };
+
_stateMachine.Start();
}
@@ -145,24 +167,21 @@ public partial class CoinJoinStateViewModel : ViewModelBase
AutoFinished,
AutoFinishedPlebStop,
- Stopped,
+ StopOrPause,
ManualPlaying,
ManualPlayingCritical,
ManualFinished,
ManualFinishedPlebStop,
+ WaitingForAutoStart,
+ Playing,
+ PlebStopActive,
}
private enum Trigger
{
Invalid = 0,
- AutoStartTimeout,
- AutoCoinJoinOn,
- AutoCoinJoinOff,
- Pause,
- Play,
- Stop,
- PlebStop,
+ PlebStopActivated,
RoundStartFailed,
RoundStart,
RoundFinished,
@@ -177,7 +196,9 @@ public partial class CoinJoinStateViewModel : ViewModelBase
WaitingForRoundMessage,
ConnectionConfirmationPhaseMessage,
EnterCriticalPhaseMessage,
- ExitCriticalPhaseMessage
+ ExitCriticalPhaseMessage,
+ WalletStartedCoinJoin,
+ WalletStoppedCoinJoin
}
private bool IsCountDownFinished => GetRemainingTime() <= TimeSpan.Zero;
@@ -186,263 +207,100 @@ public partial class CoinJoinStateViewModel : ViewModelBase
public ICommand PlayCommand { get; }
- public ICommand PauseCommand { get; }
+ public ICommand StopPauseCommand { get; }
- public ICommand StopCommand { get; }
+ public WalletViewModel WalletVm { get; }
- private void ConfigureStateMachine(CoinJoinManager coinJoinManager)
+ private void ConfigureStateMachine()
{
// See diagram in the developer docs.
_stateMachine.Configure(State.Disabled);
- // Manual Cj State
- _stateMachine.Configure(State.ManualCoinJoin)
- .Permit(Trigger.AutoCoinJoinOn, State.AutoCoinJoin)
- .InitialTransition(State.Stopped)
+ var autoStartEnd = DateTimeOffset.UtcNow;
+
+ _stateMachine.Configure(State.WaitingForAutoStart)
+ .Permit(Trigger.WalletStartedCoinJoin, State.Playing)
.OnEntry(() =>
{
- IsAuto = false;
- IsAutoWaiting = false;
PlayVisible = true;
- StopVisible = false;
PauseVisible = false;
- })
- .OnEntry(UpdateAndShowWalletMixedProgress)
- .OnTrigger(Trigger.BalanceChanged, UpdateAndShowWalletMixedProgress);
-
- _stateMachine.Configure(State.Stopped)
- .SubstateOf(State.ManualCoinJoin)
- .Permit(Trigger.Play, State.ManualPlaying)
- .OnEntry(async () =>
- {
- ProgressValue = 0;
- ElapsedTime = "";
- RemainingTime = "";
StopVisible = false;
- PlayVisible = true;
- _wallet.AllowManualCoinJoin = false;
- CurrentStatus = _stoppedMessage;
-
- await coinJoinManager.StopAsync(_wallet, CancellationToken.None);
- })
- .OnEntry(UpdateAndShowWalletMixedProgress)
- .OnTrigger(Trigger.BalanceChanged, UpdateAndShowWalletMixedProgress);
-
- _stateMachine.Configure(State.ManualPlaying)
- .SubstateOf(State.ManualCoinJoin)
- .Permit(Trigger.Stop, State.Stopped)
- .Permit(Trigger.RoundStartFailed, State.ManualFinished)
- .Permit(Trigger.PlebStop, State.ManualFinishedPlebStop)
- .Permit(Trigger.EnterCriticalPhaseMessage, State.ManualPlayingCritical)
- .OnEntry(async () =>
- {
- PlayVisible = false;
- StopVisible = true;
- CurrentStatus = _waitingMessage;
- if (_overridePlebStop && !_wallet.IsUnderPlebStop)
- {
- // If we are not below the threshold anymore, we turn off the override.
- _overridePlebStop = false;
- }
+ var now = DateTimeOffset.UtcNow;
+ autoStartEnd = now + _autoCoinJoinStartTimer.Interval;
+ _autoCoinJoinStartTimer.Start();
- await coinJoinManager.StartAsync(_wallet, true, _overridePlebStop, CancellationToken.None);
+ StartCountDown(_countDownMessage, now, autoStartEnd);
})
- .Custom(HandleMessages)
- .OnEntry(UpdateAndShowWalletMixedProgress)
- .OnTrigger(Trigger.BalanceChanged, UpdateAndShowWalletMixedProgress)
- .OnTrigger(Trigger.RoundFinished, async () => await coinJoinManager.StartAsync(_wallet, true, _overridePlebStop, CancellationToken.None))
- .OnTrigger(Trigger.Timer, UpdateCountDown)
- .OnTrigger(Trigger.Stop, () => _overridePlebStop = false);
-
- _stateMachine.Configure(State.ManualPlayingCritical)
- .SubstateOf(State.ManualPlaying)
- .Permit(Trigger.ExitCriticalPhaseMessage, State.ManualPlaying)
- .OnEntry(() =>
+ .OnExit(() =>
{
- StopVisible = false;
+ _autoCoinJoinStartTimer.Stop();
+ StopCountDown();
})
- .OnExit(() =>
+ .OnTrigger(Trigger.Timer, () =>
{
- StopVisible = true;
+ UpdateCountDown();
});
- _stateMachine.Configure(State.ManualFinished)
- .SubstateOf(State.ManualCoinJoin)
- .Permit(Trigger.Play, State.ManualPlaying)
+ _stateMachine.Configure(State.StopOrPause)
+ .Permit(Trigger.WalletStartedCoinJoin, State.Playing)
+ .Permit(Trigger.PlebStopActivated, State.PlebStopActive)
.OnEntry(() =>
{
- StopVisible = false;
PlayVisible = true;
- CurrentStatus = _finishedMessage;
- ProgressValue = 100;
- ElapsedTime = "";
- RemainingTime = "";
- });
-
- _stateMachine.Configure(State.ManualFinishedPlebStop)
- .SubstateOf(State.ManualFinished)
- .Permit(Trigger.BalanceChanged, State.ManualPlaying)
- .Permit(Trigger.PlebStopChanged, State.ManualPlaying)
- .Permit(Trigger.Stop, State.Stopped)
- .OnEntry(() =>
- {
- CurrentStatus = _plebStopMessage;
- ElapsedTime = _plebStopMessageBelow.Message ?? "";
- ProgressValue = 0;
-
- StopVisible = true;
PauseVisible = false;
- PlayVisible = true;
- })
- .OnTrigger(Trigger.Play, () =>
- {
- _overridePlebStop = true;
- });
-
- // AutoCj State
- _stateMachine.Configure(State.AutoCoinJoin)
- .InitialTransition(State.AutoStarting)
- .Permit(Trigger.AutoCoinJoinOff, State.ManualCoinJoin)
- .OnEntry(async () =>
- {
- IsAuto = true;
StopVisible = false;
- PauseVisible = false;
- PlayVisible = false;
-
- CurrentStatus = _initialisingMessage;
- await coinJoinManager.StopAsync(_wallet, CancellationToken.None);
- });
-
- _stateMachine.Configure(State.AutoStarting)
- .SubstateOf(State.AutoCoinJoin)
- .Permit(Trigger.Pause, State.Paused)
- .Permit(Trigger.RoundStart, State.AutoPlaying)
- .Permit(Trigger.AutoStartTimeout, State.AutoPlaying)
- .Permit(Trigger.Play, State.AutoPlaying)
- .Permit(Trigger.RoundStartFailed, State.AutoFinished)
- .OnEntry(() =>
- {
- PlayVisible = true;
- IsAutoWaiting = true;
- var now = DateTimeOffset.UtcNow;
- StartCountDown(_countDownMessage, start: now, end: now + TimeSpan.FromSeconds(Random.Shared.Next(5 * 60, 16 * 60)));
+ CurrentStatus = IsAutoCoinJoinEnabled ? _pauseMessage : _stoppedMessage;
+ ElapsedTime = "Press Play to start";
})
- .OnTrigger(Trigger.Timer, UpdateAutoStartCountDown)
.OnExit(() =>
{
- StopCountDown();
- IsAutoWaiting = false;
- });
-
- _stateMachine.Configure(State.Paused)
- .SubstateOf(State.AutoCoinJoin)
- .Permit(Trigger.Play, State.AutoPlaying)
- .OnEntry(async () =>
- {
- IsAutoWaiting = true;
-
- CurrentStatus = _pauseMessage;
- ProgressValue = 0;
ElapsedTime = "";
- RemainingTime = "";
-
- PauseVisible = false;
- PlayVisible = true;
-
- await coinJoinManager.StopAsync(_wallet, CancellationToken.None);
- })
- .OnEntry(UpdateAndShowWalletMixedProgress)
- .OnTrigger(Trigger.BalanceChanged, UpdateAndShowWalletMixedProgress);
+ });
- _stateMachine.Configure(State.AutoPlaying)
- .SubstateOf(State.AutoCoinJoin)
- .Permit(Trigger.Pause, State.Paused)
- .Permit(Trigger.PlebStop, State.AutoFinishedPlebStop)
- .Permit(Trigger.RoundStartFailed, State.AutoFinished)
- .Permit(Trigger.EnterCriticalPhaseMessage, State.AutoPlayingCritical)
- .OnEntry(async () =>
+ _stateMachine.Configure(State.Playing)
+ .Permit(Trigger.WalletStoppedCoinJoin, State.StopOrPause)
+ .Permit(Trigger.PlebStopActivated, State.PlebStopActive)
+ .OnEntry(() =>
{
- CurrentStatus = _waitingMessage;
- IsAutoWaiting = false;
- PauseVisible = true;
PlayVisible = false;
+ PauseVisible = IsAutoCoinJoinEnabled;
+ StopVisible = !IsAutoCoinJoinEnabled;
- if (_overridePlebStop && !_wallet.IsUnderPlebStop)
- {
- // If we are not below the threshold anymore, we turn off the override.
- _overridePlebStop = false;
- }
-
- await coinJoinManager.StartAsync(_wallet, false, _overridePlebStop, CancellationToken.None);
+ CurrentStatus = _waitingMessage;
})
- .Custom(HandleMessages)
- .OnEntry(UpdateAndShowWalletMixedProgress)
- .OnTrigger(Trigger.BalanceChanged, UpdateAndShowWalletMixedProgress)
- .OnTrigger(Trigger.Timer, UpdateCountDown)
- .OnTrigger(Trigger.Pause, () => _overridePlebStop = false);
-
- _stateMachine.Configure(State.AutoPlayingCritical)
- .SubstateOf(State.AutoPlaying)
- .Permit(Trigger.ExitCriticalPhaseMessage, State.AutoPlaying)
- .OnEntry(() =>
+ .OnTrigger(Trigger.EnterCriticalPhaseMessage, () =>
{
PauseVisible = false;
+ StopVisible = false;
})
- .OnExit(() =>
+ .OnTrigger(Trigger.ExitCriticalPhaseMessage, () =>
{
- PauseVisible = true;
+ PauseVisible = IsAutoCoinJoinEnabled;
+ StopVisible = !IsAutoCoinJoinEnabled;
});
- _stateMachine.Configure(State.AutoFinished)
- .SubstateOf(State.AutoCoinJoin)
- .Permit(Trigger.RoundStart, State.AutoPlaying)
- .Permit(Trigger.BalanceChanged, State.AutoPlaying)
+ _stateMachine.Configure(State.PlebStopActive)
+ .Permit(Trigger.BalanceChanged, State.Playing)
+ .Permit(Trigger.PlebStopChanged, State.Playing)
+ .Permit(Trigger.WalletStartedCoinJoin, State.Playing)
+ .Permit(Trigger.WalletStoppedCoinJoin, State.StopOrPause)
.OnEntry(() =>
{
+ PlayVisible = true;
PauseVisible = false;
- PlayVisible = false;
-
- ProgressValue = 100;
- ElapsedTime = "";
- RemainingTime = "";
-
- CurrentStatus = _finishedMessage;
- });
+ StopVisible = false;
- _stateMachine.Configure(State.AutoFinishedPlebStop)
- .SubstateOf(State.AutoFinished)
- .Permit(Trigger.Play, State.AutoPlaying)
- .Permit(Trigger.PlebStopChanged, State.AutoPlaying)
- .Permit(Trigger.Pause, State.Paused)
- .OnEntry(() =>
- {
CurrentStatus = _plebStopMessage;
- ElapsedTime = _plebStopMessageBelow.Message ?? "";
- ProgressValue = 0;
-
- StopVisible = false;
- PauseVisible = true;
- PlayVisible = true;
+ ElapsedTime = _plebStopMessageBelow.Message!;
})
- .OnTrigger(Trigger.Play, () =>
+ .OnExit(() =>
{
- _overridePlebStop = true;
+ ElapsedTime = "";
});
}
- private void UpdateAutoStartCountDown()
- {
- UpdateCountDown();
-
- if (IsCountDownFinished)
- {
- _stateMachine.Fire(Trigger.AutoStartTimeout);
- }
- }
-
private void UpdateCountDown()
{
IsCountDownDelayHappening = IsCounting && IsCountDownFinished;
@@ -495,6 +353,14 @@ public partial class CoinJoinStateViewModel : ViewModelBase
{
switch (e)
{
+ case WalletStartedCoinJoinEventArgs:
+ _stateMachine.Fire(Trigger.WalletStartedCoinJoin);
+ break;
+
+ case WalletStoppedCoinJoinEventArgs:
+ _stateMachine.Fire(Trigger.WalletStoppedCoinJoin);
+ break;
+
case CompletedEventArgs:
_stateMachine.Fire(Trigger.RoundFinished);
break;
@@ -504,7 +370,7 @@ public partial class CoinJoinStateViewModel : ViewModelBase
break;
case StartErrorEventArgs start when start.Error is CoinjoinError.NotEnoughUnprivateBalance:
- _stateMachine.Fire(Trigger.PlebStop);
+ _stateMachine.Fire(Trigger.PlebStopActivated);
break;
case StartErrorEventArgs:
@@ -673,9 +539,4 @@ public partial class CoinJoinStateViewModel : ViewModelBase
{
_stateMachine.Fire(Trigger.Timer);
}
-
- private void SetAutoCoinJoin(bool enabled)
- {
- _stateMachine.Fire(enabled ? Trigger.AutoCoinJoinOn : Trigger.AutoCoinJoinOff);
- }
}

MODIFY
WalletWasabi.Fluent/Views/Wallets/MusicControlsView.axaml
WalletWasabi.Fluent/Views/Wallets/MusicControlsView.axaml
<Button Classes="plain"
IsVisible="{Binding PauseVisible}"
- Command="{Binding PauseCommand}">
+ Command="{Binding StopPauseCommand}">
<PathIcon Data="{StaticResource pause_regular}" />
</Button>
<Button Classes="plain"
IsVisible="{Binding StopVisible}"
- Command="{Binding StopCommand}">
+ Command="{Binding StopPauseCommand}">
<PathIcon Data="{StaticResource stop_regular}" />
</Button>

MODIFY
WalletWasabi/WabiSabi/Client/CoinJoinManager.cs
WalletWasabi/WabiSabi/Client/CoinJoinManager.cs
@@ -140,6 +140,8 @@ public class CoinJoinManager : BackgroundService
return;
}
+ NotifyWalletStartedCoinJoin(walletToStart);
+
if (walletToStart.IsUnderPlebStop && !startCommand.OverridePlebStop)
{
walletToStart.LogDebug("PlebStop preventing coinjoin.");
@@ -208,12 +210,11 @@ public class CoinJoinManager : BackgroundService
if (trackedCoinJoins.TryGetValue(walletToStop.WalletName, out var coinJoinTrackerToStop))
{
+ coinJoinTrackerToStop.Stop();
if (coinJoinTrackerToStop.InCriticalCoinJoinState)
{
walletToStop.LogWarning($"Coinjoin is in critical phase, it cannot be stopped.");
- return;
}
- coinJoinTrackerToStop.Stop();
}
}
@@ -372,6 +373,10 @@ public class CoinJoinManager : BackgroundService
await StartAsync(finishedCoinJoin.Wallet, finishedCoinJoin.StopWhenAllMixed, finishedCoinJoin.OverridePlebStop, cancellationToken).ConfigureAwait(false);
}
+ else
+ {
+ NotifyWalletStoppedCoinJoin(wallet);
+ }
}
/// <summary>
@@ -390,6 +395,12 @@ public class CoinJoinManager : BackgroundService
}
}
+ private void NotifyWalletStartedCoinJoin(Wallet openedWallet) =>
+ StatusChanged.SafeInvoke(this, new WalletStartedCoinJoinEventArgs(openedWallet));
+
+ private void NotifyWalletStoppedCoinJoin(Wallet openedWallet) =>
+ StatusChanged.SafeInvoke(this, new WalletStoppedCoinJoinEventArgs(openedWallet));
+
private void NotifyCoinJoinStarted(Wallet openedWallet, TimeSpan registrationTimeout) =>
StatusChanged.SafeInvoke(this, new StartedEventArgs(openedWallet, registrationTimeout));

MODIFY
WalletWasabi/WabiSabi/Client/CoinJoinTracker.cs
WalletWasabi/WabiSabi/Client/CoinJoinTracker.cs
@@ -48,8 +48,11 @@ public class CoinJoinTracker : IDisposable
public void Stop()
{
IsStopped = true;
+ if (!InCriticalCoinJoinState)
+ {
CancellationTokenSource.Cancel();
}
+ }
private void CoinJoinClient_CoinJoinClientProgress(object? sender, CoinJoinProgressEventArgs coinJoinProgressEventArgs)
{

ADD
WalletWasabi/WabiSabi/Client/StatusChangedEvents/WalletStartedCoinJoinEventArgs.cs
+using WalletWasabi.Wallets;
+
+namespace WalletWasabi.WabiSabi.Client.StatusChangedEvents;
+
+public class WalletStartedCoinJoinEventArgs : StatusChangedEventArgs
+{
+ public WalletStartedCoinJoinEventArgs(Wallet wallet) : base(wallet)
+ {
+ }
+}

ADD
WalletWasabi/WabiSabi/Client/StatusChangedEvents/WalletStoppedCoinJoinEventArgs.cs
+using WalletWasabi.Wallets;
+
+namespace WalletWasabi.WabiSabi.Client.StatusChangedEvents;
+
+public class WalletStoppedCoinJoinEventArgs : StatusChangedEventArgs
+{
+ public WalletStoppedCoinJoinEventArgs(Wallet wallet) : base(wallet)
+ {
+ }
+}

MODIFY
WalletWasabi/Wallets/Wallet.cs
WalletWasabi/Wallets/Wallet.cs
@@ -91,8 +91,6 @@ public class Wallet : BackgroundService
public bool IsLoggedIn { get; private set; }
- public bool AllowManualCoinJoin { get; set; }
-
public Kitchen Kitchen { get; } = new();
public ICoinsView NonPrivateCoins => new CoinsView(Coins.Where(c => c.HdPubKey.AnonymitySet < KeyManager.AnonScoreTarget));

Refactoring musicbox