Add reason to round abortion.
Change some "failed" logging messages, so it'll be more convenient to look for errors in CI.
MODIFY
WalletWasabi.Backend/Controllers/ChaumianCoinJoinController.cs
WalletWasabi.Backend/Controllers/ChaumianCoinJoinController.cs
@@ -344,7 +344,7 @@ namespace WalletWasabi.Backend.Controllers
int aliceCountAfterConnectionConfirmationTimeout = round.CountAlices();
if (aliceCountAfterConnectionConfirmationTimeout < 2)
{
- round.Fail();
+ round.Abort(nameof(ChaumianCoinJoinController), $"Only {aliceCountAfterConnectionConfirmationTimeout} Alices confiremd their connection.");
}
else
{

MODIFY
WalletWasabi.Backend/Global.cs
WalletWasabi.Backend/Global.cs
@@ -69,7 +69,7 @@ namespace WalletWasabi.Backend
{
Coordinator.UpdateRoundConfig(RoundConfig);
- Coordinator.FailAllRoundsInInputRegistration();
+ Coordinator.AbortAllRoundsInInputRegistration(nameof(ConfigWatcher), $"{nameof(RoundConfig)} has changed.");
}
catch (Exception ex)
{
@@ -78,7 +78,7 @@ namespace WalletWasabi.Backend
return Task.CompletedTask;
}); // Every 10 seconds check the config
- Logger.LogInfo<ConfigWatcher>("RoundConfigWatcher is successfully started.");
+ Logger.LogInfo<ConfigWatcher>($"{nameof(RoundConfigWatcher)} is successfully started.");
}
}

MODIFY
WalletWasabi.Tests/RegTests.cs
WalletWasabi.Tests/RegTests.cs
@@ -1656,7 +1656,7 @@ namespace WalletWasabi.Tests
int connectionConfirmationTimeout = 50;
var roundConfig = new CcjRoundConfig(denomination, 2, coordinatorFeePercent, anonymitySet, 100, connectionConfirmationTimeout, 50, 50, 2);
coordinator.UpdateRoundConfig(roundConfig);
- coordinator.FailAllRoundsInInputRegistration();
+ coordinator.AbortAllRoundsInInputRegistration(nameof(RegTests), "");
Uri baseUri = new Uri(RegTestFixture.BackendEndPoint);
using (var torClient = new TorHttpClient(baseUri))
@@ -1747,20 +1747,20 @@ namespace WalletWasabi.Tests
roundConfig.Denomination = Money.Coins(0.01m); // exactly the same as our output
coordinator.UpdateRoundConfig(roundConfig);
- coordinator.FailAllRoundsInInputRegistration();
+ coordinator.AbortAllRoundsInInputRegistration(nameof(RegTests), "");
httpRequestException = await Assert.ThrowsAsync<HttpRequestException>(async () => await AliceClient.CreateNewAsync(network, inputsRequest, baseUri));
Assert.StartsWith($"{HttpStatusCode.BadRequest.ToReasonString()}\nNot enough inputs are provided. Fee to pay:", httpRequestException.Message);
roundConfig.Denomination = Money.Coins(0.00999999m); // one satoshi less than our output
coordinator.UpdateRoundConfig(roundConfig);
- coordinator.FailAllRoundsInInputRegistration();
+ coordinator.AbortAllRoundsInInputRegistration(nameof(RegTests), "");
httpRequestException = await Assert.ThrowsAsync<HttpRequestException>(async () => await AliceClient.CreateNewAsync(network, inputsRequest, baseUri));
Assert.StartsWith($"{HttpStatusCode.BadRequest.ToReasonString()}\nNot enough inputs are provided. Fee to pay:", httpRequestException.Message);
roundConfig.Denomination = Money.Coins(0.008m); // one satoshi less than our output
roundConfig.ConnectionConfirmationTimeout = 2;
coordinator.UpdateRoundConfig(roundConfig);
- coordinator.FailAllRoundsInInputRegistration();
+ coordinator.AbortAllRoundsInInputRegistration(nameof(RegTests), "");
using (var aliceClient = await AliceClient.CreateNewAsync(network, inputsRequest, baseUri))
{
Assert.NotNull(aliceClient.BlindedOutputSignature);
@@ -1849,7 +1849,7 @@ namespace WalletWasabi.Tests
roundConfig.ConnectionConfirmationTimeout = 1; // One second.
coordinator.UpdateRoundConfig(roundConfig);
- coordinator.FailAllRoundsInInputRegistration();
+ coordinator.AbortAllRoundsInInputRegistration(nameof(RegTests), "");
roundState = await satoshiClient.GetRoundStateAsync(aliceClient.RoundId);
Assert.Equal(CcjRoundPhase.ConnectionConfirmation, roundState.Phase);
@@ -1940,7 +1940,7 @@ namespace WalletWasabi.Tests
roundConfig.ConnectionConfirmationTimeout = 60;
coordinator.UpdateRoundConfig(roundConfig);
- coordinator.FailAllRoundsInInputRegistration();
+ coordinator.AbortAllRoundsInInputRegistration(nameof(RegTests), "");
httpRequestException = await Assert.ThrowsAsync<HttpRequestException>(async () => await aliceClient.PostConfirmationAsync());
Assert.Equal($"{HttpStatusCode.Gone.ToReasonString()}\nRound is not running.", httpRequestException.Message);
}
@@ -2125,7 +2125,7 @@ namespace WalletWasabi.Tests
int connectionConfirmationTimeout = 120;
var roundConfig = new CcjRoundConfig(denomination, 140, coordinatorFeePercent, anonymitySet, 240, connectionConfirmationTimeout, 1, 1, 1);
coordinator.UpdateRoundConfig(roundConfig);
- coordinator.FailAllRoundsInInputRegistration();
+ coordinator.AbortAllRoundsInInputRegistration(nameof(RegTests), "");
await rpc.GenerateAsync(3); // So to make sure we have enough money.
Uri baseUri = new Uri(RegTestFixture.BackendEndPoint);
@@ -2261,7 +2261,7 @@ namespace WalletWasabi.Tests
int connectionConfirmationTimeout = 120;
var roundConfig = new CcjRoundConfig(denomination, 144, coordinatorFeePercent, anonymitySet, 240, connectionConfirmationTimeout, 50, 50, 1);
coordinator.UpdateRoundConfig(roundConfig);
- coordinator.FailAllRoundsInInputRegistration();
+ coordinator.AbortAllRoundsInInputRegistration(nameof(RegTests), "");
await rpc.GenerateAsync(100); // So to make sure we have enough money.
Uri baseUri = new Uri(RegTestFixture.BackendEndPoint);
@@ -2468,7 +2468,7 @@ namespace WalletWasabi.Tests
int connectionConfirmationTimeout = 14;
var roundConfig = new CcjRoundConfig(denomination, 140, coordinatorFeePercent, anonymitySet, 240, connectionConfirmationTimeout, 50, 50, 1);
coordinator.UpdateRoundConfig(roundConfig);
- coordinator.FailAllRoundsInInputRegistration();
+ coordinator.AbortAllRoundsInInputRegistration(nameof(RegTests), "");
await rpc.GenerateAsync(3); // So to make sure we have enough money.
var keyManager = KeyManager.CreateNew(out _, password);
var key1 = keyManager.GenerateNewKey("foo", KeyState.Clean, false);
@@ -2568,7 +2568,7 @@ namespace WalletWasabi.Tests
connectionConfirmationTimeout = 1;
roundConfig = new CcjRoundConfig(denomination, 140, coordinatorFeePercent, anonymitySet, 240, connectionConfirmationTimeout, 50, 50, 1);
coordinator.UpdateRoundConfig(roundConfig);
- coordinator.FailAllRoundsInInputRegistration();
+ coordinator.AbortAllRoundsInInputRegistration(nameof(RegTests), "");
Assert.NotEmpty(chaumianClient1.State.GetAllQueuedCoins());
await chaumianClient1.DequeueAllCoinsFromMixAsync();
Assert.Empty(chaumianClient1.State.GetAllQueuedCoins());
@@ -2613,7 +2613,7 @@ namespace WalletWasabi.Tests
int connectionConfirmationTimeout = 14;
var roundConfig = new CcjRoundConfig(denomination, 140, coordinatorFeePercent, anonymitySet, 240, connectionConfirmationTimeout, 50, 50, 1);
coordinator.UpdateRoundConfig(roundConfig);
- coordinator.FailAllRoundsInInputRegistration();
+ coordinator.AbortAllRoundsInInputRegistration(nameof(RegTests), "");
await rpc.GenerateAsync(3); // So to make sure we have enough money.
var keyManager = KeyManager.CreateNew(out _, password);
var key1 = keyManager.GenerateNewKey("foo", KeyState.Clean, false);
@@ -2710,7 +2710,7 @@ namespace WalletWasabi.Tests
int connectionConfirmationTimeout = 14;
var roundConfig = new CcjRoundConfig(denomination, 140, coordinatorFeePercent, anonymitySet, 240, connectionConfirmationTimeout, 50, 50, 1);
coordinator.UpdateRoundConfig(roundConfig);
- coordinator.FailAllRoundsInInputRegistration();
+ coordinator.AbortAllRoundsInInputRegistration(nameof(RegTests), "");
// Create the services.
// 1. Create connection service.

MODIFY
WalletWasabi/Models/ChaumianCoinJoin/CcjRound.cs
WalletWasabi/Models/ChaumianCoinJoin/CcjRound.cs
@@ -159,7 +159,7 @@ namespace WalletWasabi.Models.ChaumianCoinJoin
}
catch (Exception ex)
{
- Logger.LogError<CcjRound>($"Round ({RoundId}) creation failed.");
+ Logger.LogError<CcjRound>($"Round ({RoundId}): Could not create.");
Logger.LogError<CcjRound>(ex);
throw;
}
@@ -211,7 +211,7 @@ namespace WalletWasabi.Models.ChaumianCoinJoin
Status = CcjRoundStatus.Running;
}
- else if (Status != CcjRoundStatus.Running) // Failed or succeeded, swallow
+ else if (Status != CcjRoundStatus.Running) // Aborted or succeeded, swallow
{
return;
}
@@ -320,7 +320,7 @@ namespace WalletWasabi.Models.ChaumianCoinJoin
catch (Exception ex)
{
Logger.LogError<CcjRound>(ex);
- Status = CcjRoundStatus.Failed;
+ Status = CcjRoundStatus.Aborted;
throw;
}
}
@@ -353,14 +353,14 @@ namespace WalletWasabi.Models.ChaumianCoinJoin
// Delay asyncronously to the requested timeout.
await Task.Delay(timeout);
- var executeRunFailure = false;
+ var executeRunAbortion = false;
using (await RoundSyncronizerLock.LockAsync())
{
- executeRunFailure = Status == CcjRoundStatus.Running && Phase == expectedPhase;
+ executeRunAbortion = Status == CcjRoundStatus.Running && Phase == expectedPhase;
}
- if (executeRunFailure)
+ if (executeRunAbortion)
{
- Logger.LogInfo<CcjRound>($"Round ({RoundId}): {expectedPhase.ToString()} timed out after {timeout.TotalSeconds} seconds. Failure mode is executing.");
+ Logger.LogInfo<CcjRound>($"Round ({RoundId}): {expectedPhase.ToString()} timed out after {timeout.TotalSeconds} seconds. Aborting...");
// This will happen outside the lock.
Task.Run(async () =>
@@ -371,13 +371,13 @@ namespace WalletWasabi.Models.ChaumianCoinJoin
{
case CcjRoundPhase.InputRegistration:
{
- // Only fail if less than two one Alice is registered.
+ // Only abort if less than two one Alice is registered.
// Don't ban anyone, it's ok if they lost connection.
await RemoveAlicesIfInputsSpentAsync();
int aliceCountAfterInputRegistrationTimeout = CountAlices();
if (aliceCountAfterInputRegistrationTimeout < 2)
{
- Fail();
+ Abort(nameof(CcjRound), $"Only {aliceCountAfterInputRegistrationTimeout} Alices registered.");
}
else
{
@@ -390,7 +390,7 @@ namespace WalletWasabi.Models.ChaumianCoinJoin
case CcjRoundPhase.ConnectionConfirmation:
{
- // Only fail if less than two one alices are registered.
+ // Only abort if less than two one alices are registered.
// What if an attacker registers all the time many alices, then drops out. He'll achieve only 2 alices to participate?
// If he registers many alices at InputRegistration
// AND never confirms in connection confirmation
@@ -411,7 +411,7 @@ namespace WalletWasabi.Models.ChaumianCoinJoin
int aliceCountAfterConnectionConfirmationTimeout = CountAlices();
if (aliceCountAfterConnectionConfirmationTimeout < 2)
{
- Fail();
+ Abort(nameof(CcjRound), $"Only {aliceCountAfterConnectionConfirmationTimeout} Alices confiremd their connection.");
}
else
{
@@ -424,9 +424,9 @@ namespace WalletWasabi.Models.ChaumianCoinJoin
case CcjRoundPhase.OutputRegistration:
{
- // Output registration never fails.
+ // Output registration never aborts.
// We don't know which Alice to ban.
- // Therefore proceed to signing, and whichever Alice doesn't sign ban.
+ // Therefore proceed to signing, and whichever Alice doesn't sign, ban her.
await ExecuteNextPhaseAsync(CcjRoundPhase.Signing);
}
break;
@@ -448,7 +448,7 @@ namespace WalletWasabi.Models.ChaumianCoinJoin
{
await UtxoReferee.BanUtxosAsync(1, DateTimeOffset.UtcNow, outpointsToBan.ToArray());
}
- Fail();
+ Abort(nameof(CcjRound), "Not all Alices signed.");
}
break;
@@ -481,20 +481,30 @@ namespace WalletWasabi.Models.ChaumianCoinJoin
Logger.LogInfo<CcjRound>($"Round ({RoundId}): Succeeded.");
}
- public void Fail(bool syncLock = true)
+ public void Abort(string loggingCategory, string reason, bool syncLock = true)
{
if (syncLock)
{
using (RoundSyncronizerLock.Lock())
{
- Status = CcjRoundStatus.Failed;
+ Status = CcjRoundStatus.Aborted;
}
}
else
{
- Status = CcjRoundStatus.Failed;
+ Status = CcjRoundStatus.Aborted;
+ }
+
+ string category = string.IsNullOrWhiteSpace(loggingCategory) ? loggingCategory : nameof(CcjRound);
+
+ if (string.IsNullOrWhiteSpace(reason))
+ {
+ Logger.LogInfo($"Round ({RoundId}): Aborted.", category);
+ }
+ else
+ {
+ Logger.LogInfo($"Round ({RoundId}): Aborted. Reason: {reason}", category);
}
- Logger.LogInfo<CcjRound>($"Round ({RoundId}): Failed.");
}
public int CountAlices(bool syncLock = true)
@@ -675,9 +685,8 @@ namespace WalletWasabi.Models.ChaumianCoinJoin
}
catch (Exception ex)
{
- Logger.LogError<CcjRound>($"Round ({RoundId}): Failed to broadcast the CoinJoin: {SignedCoinJoin.GetHash()}.");
+ Abort(nameof(CcjRound), $"Could not broadcast the CoinJoin: {SignedCoinJoin.GetHash()}.", syncLock: false);
Logger.LogError<CcjRound>(ex);
- Fail(syncLock: false);
}
}
}

MODIFY
WalletWasabi/Models/ChaumianCoinJoin/CcjRoundStatus.cs
WalletWasabi/Models/ChaumianCoinJoin/CcjRoundStatus.cs
NotStarted,
Running,
Succeded,
- Failed
+ Aborted
}
}

MODIFY
WalletWasabi/Services/CcjCoordinator.cs
WalletWasabi/Services/CcjCoordinator.cs
@@ -195,8 +195,8 @@ namespace WalletWasabi.Services
}
}
- // If failed in signing phase, then ban Alices those didn't sign.
- if (status == CcjRoundStatus.Failed && round.Phase == CcjRoundPhase.Signing)
+ // If aborted in signing phase, then ban Alices those didn't sign.
+ if (status == CcjRoundStatus.Aborted && round.Phase == CcjRoundPhase.Signing)
{
foreach (Alice alice in round.GetAlicesByNot(AliceState.SignedCoinJoin, syncLock: false)) // Because the event sometimes is raised from inside the lock.
{
@@ -207,20 +207,21 @@ namespace WalletWasabi.Services
}
// If finished start a new round.
- if (status == CcjRoundStatus.Failed || status == CcjRoundStatus.Succeded)
+ if (status == CcjRoundStatus.Aborted || status == CcjRoundStatus.Succeded)
{
round.StatusChanged -= Round_StatusChangedAsync;
await MakeSureTwoRunningRoundsAsync();
}
}
- public void FailAllRoundsInInputRegistration()
+ public void AbortAllRoundsInInputRegistration(string loggingCategory, string reason)
{
+ string category = string.IsNullOrWhiteSpace(loggingCategory) ? nameof(CcjCoordinator) : loggingCategory;
using (RoundsListLock.Lock())
{
foreach (var r in Rounds.Where(x => x.Status == CcjRoundStatus.Running && x.Phase == CcjRoundPhase.InputRegistration))
{
- r.Fail();
+ r.Abort(category, reason);
}
}
}
@@ -306,7 +307,7 @@ namespace WalletWasabi.Services
}
catch (Exception ex)
{
- // if failed remove from everywhere (should not happen normally)
+ // If aborted remove from everywhere (should not happen normally).
UnconfirmedCoinJoins.Remove(cjHash);
CoinJoins.Remove(cjHash);
await File.WriteAllLinesAsync(CoinJoinsFilePath, CoinJoins.Select(x => x.ToString()));

Add reason to round abortion.
Change some "failed" logging messages, so it'll be more convenient to look for errors in CI.