Implement confirmation
MODIFY
WalletWasabi.Backend/Controllers/ChaumianCoinJoinController.cs
WalletWasabi.Backend/Controllers/ChaumianCoinJoinController.cs
@@ -269,16 +269,20 @@ namespace WalletWasabi.Backend.Controllers
}
/// <summary>
- /// Alice asks for the final CoinJoin transaction.
+ /// Alice must confirm her participation periodically in InputRegistration phase and confirm once in ConnectionConfirmation phase.
/// </summary>
/// <param name="uniqueId">Unique identifier, obtained previously.</param>
- /// <returns>The coinjoin Transaction.</returns>
- /// <response code="200">Returns the coinjoin transaction.</response>
+ /// <returns>RoundHash if the phase is already ConnectionConfirmation.</returns>
+ /// <response code="200">RoundHash if the phase is already ConnectionConfirmation.</response>
+ /// <response code="204">If the phase is InputRegistration and Alice is found.</response>
/// <response code="400">The provided uniqueId was malformed.</response>
- [HttpGet("coinjoin/{uniqueId}")]
+ /// <response code="404">If Alice is not found.</response>
+ [HttpPost("confirmation/{uniqueId}")]
[ProducesResponseType(200)]
+ [ProducesResponseType(204)]
[ProducesResponseType(400)]
- public IActionResult GetCoinJoin(string uniqueId)
+ [ProducesResponseType(404)]
+ public async Task<IActionResult> PostConfirmationAsync(string uniqueId)
{
CheckUniqueId(uniqueId, out IActionResult returnFailureResponse);
if (returnFailureResponse != null)
@@ -286,22 +290,67 @@ namespace WalletWasabi.Backend.Controllers
return returnFailureResponse;
}
- return Ok();
+ var uniqueIdGuid = Guid.Parse(uniqueId);
+ var roundAlice = Coordinator.TryGetRoundAndAliceBy(uniqueIdGuid);
+ if (roundAlice.alice == null)
+ {
+ return NotFound("Alice is not registered to any running rounds.");
+ }
+
+ switch (roundAlice.round.Phase)
+ {
+ case CcjRoundPhase.InputRegistration:
+ {
+ roundAlice.round.StartAliceTimeout(uniqueIdGuid);
+ return NoContent();
+ }
+ case CcjRoundPhase.ConnectionConfirmation:
+ {
+ roundAlice.alice.State = AliceState.ConnectionConfirmed;
+
+ // Progress round if needed.
+ if (roundAlice.round.AllAlices(AliceState.ConnectionConfirmed))
+ {
+
+ IEnumerable<Alice> alicesToBan = await roundAlice.round.RemoveAlicesIfInputsSpentAsync(); // So ban only those who confirmed participation, yet spent their inputs.
+
+ if (alicesToBan.Count() > 0)
+ {
+ await Coordinator.UtxoReferee.BanUtxosAsync(1, DateTimeOffset.Now, alicesToBan.SelectMany(x => x.Inputs).Select(y => y.OutPoint).ToArray());
+ }
+
+ int aliceCountAfterConnectionConfirmationTimeout = roundAlice.round.CountAlices();
+ if (aliceCountAfterConnectionConfirmationTimeout < 2)
+ {
+ roundAlice.round.Fail();
+ }
+ else
+ {
+ roundAlice.round.UpdateAnonymitySet(aliceCountAfterConnectionConfirmationTimeout);
+ // Progress to the next phase, which will be OutputRegistration
+ await roundAlice.round.ExecuteNextPhaseAsync(CcjRoundPhase.OutputRegistration);
+ }
+ }
+
+ return Ok(roundAlice.round.RoundHash);
+ }
+ default: // This should never happen?
+ {
+ return NotFound();
+ }
+ }
}
/// <summary>
- /// Alice must confirm her participation periodically in InputRegistration phase and confirm once in ConnectionConfirmation phase.
+ /// Alice can revoke her registration without penalty if the current phase is InputRegistration.
/// </summary>
/// <param name="uniqueId">Unique identifier, obtained previously.</param>
- /// <returns>RoundHash if the phase is already ConnectionConfirmation.</returns>
- /// <response code="200">RoundHash if the phase is already ConnectionConfirmation.</response>
- /// <response code="204">If the phase is not ConnectionConfirmation.</response>
+ /// <response code="204">Alice sucessfully uncofirmed her participation.</response>
/// <response code="400">The provided uniqueId was malformed.</response>
- [HttpPost("confirmation/{uniqueId}")]
- [ProducesResponseType(200)]
+ [HttpPost("unconfirmation/{uniqueId}")]
[ProducesResponseType(204)]
[ProducesResponseType(400)]
- public IActionResult PostConfirmation(string uniqueId)
+ public IActionResult PostUncorfimation(string uniqueId)
{
CheckUniqueId(uniqueId, out IActionResult returnFailureResponse);
if (returnFailureResponse != null)
@@ -309,19 +358,20 @@ namespace WalletWasabi.Backend.Controllers
return returnFailureResponse;
}
- return Ok();
+ return NoContent();
}
/// <summary>
- /// Alice can revoke her registration without penalty if the current phase is InputRegistration.
+ /// Alice asks for the final CoinJoin transaction.
/// </summary>
/// <param name="uniqueId">Unique identifier, obtained previously.</param>
- /// <response code="204">Alice sucessfully uncofirmed her participation.</response>
+ /// <returns>The coinjoin Transaction.</returns>
+ /// <response code="200">Returns the coinjoin transaction.</response>
/// <response code="400">The provided uniqueId was malformed.</response>
- [HttpPost("unconfirmation/{uniqueId}")]
- [ProducesResponseType(204)]
+ [HttpGet("coinjoin/{uniqueId}")]
+ [ProducesResponseType(200)]
[ProducesResponseType(400)]
- public IActionResult PostUncorfimation(string uniqueId)
+ public IActionResult GetCoinJoin(string uniqueId)
{
CheckUniqueId(uniqueId, out IActionResult returnFailureResponse);
if(returnFailureResponse != null)
@@ -329,7 +379,7 @@ namespace WalletWasabi.Backend.Controllers
return returnFailureResponse;
}
- return NoContent();
+ return Ok();
}
private void CheckUniqueId(string uniqueId, out IActionResult returnFailureResponse)

MODIFY
WalletWasabi/ChaumianCoinJoin/CcjRound.cs
WalletWasabi/ChaumianCoinJoin/CcjRound.cs
@@ -442,6 +442,14 @@ namespace WalletWasabi.ChaumianCoinJoin
}
}
+ public bool AllAlices(AliceState state)
+ {
+ using (RoundSyncronizerLock.Lock())
+ {
+ return Alices.All(x=>x.State == state);
+ }
+ }
+
public bool ContainsBlindedOutput(string blindedOutputHex, out List<Alice> alices)
{
alices = new List<Alice>();
@@ -508,6 +516,14 @@ namespace WalletWasabi.ChaumianCoinJoin
}
}
+ public Alice TryGetAliceBy(Guid uniqueId)
+ {
+ using (RoundSyncronizerLock.Lock())
+ {
+ return Alices.SingleOrDefault(x => x.UniqueId == uniqueId);
+ }
+ }
+
public IEnumerable<Alice> GetAlicesByNot(AliceState state)
{
using (RoundSyncronizerLock.Lock())

MODIFY
WalletWasabi/Services/CcjCoordinator.cs
WalletWasabi/Services/CcjCoordinator.cs
@@ -280,6 +280,27 @@ namespace WalletWasabi.Services
}
}
+ public (CcjRound round, Alice alice) TryGetRoundAndAliceBy(Guid uniqueId)
+ {
+ using (RoundsListLock.Lock())
+ {
+ Alice alice = null;
+ CcjRound round = null;
+ foreach (var r in Rounds.Where(x => x.Status == CcjRoundStatus.Running))
+ {
+ var a = round.TryGetAliceBy(uniqueId);
+ if (a != null)
+ {
+ alice = a;
+ round = r;
+ break;
+ }
+ }
+
+ return (round, alice);
+ }
+ }
+
public bool AnyRunningRoundContainsInput(OutPoint input, out List<Alice> alices)
{
using (RoundsListLock.Lock())
@@ -374,6 +395,7 @@ namespace WalletWasabi.Services
Dispose(true);
// GC.SuppressFinalize(this);
}
+
#endregion
}
}

Implement confirmation