Remove RoundHash
MODIFY
WalletWasabi.Backend/Controllers/ChaumianCoinJoinController.cs
WalletWasabi.Backend/Controllers/ChaumianCoinJoinController.cs
@@ -306,15 +306,13 @@ namespace WalletWasabi.Backend.Controllers
/// </summary>
/// <param name="uniqueId">Unique identifier, obtained previously.</param>
/// <param name="roundId">Round 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 InputRegistration and Alice is found.</response>
+ /// <returns>Current Phase and Alice is found.</returns>
+ /// <response code="200">Current Phase and Alice is found.</response>
/// <response code="400">The provided uniqueId or roundId was malformed.</response>
/// <response code="404">If Alice or the round is not found.</response>
/// <response code="410">Participation can be only confirmed from a Running round's InputRegistration or ConnectionConfirmation phase.</response>
[HttpPost("confirmation")]
[ProducesResponseType(200)]
- [ProducesResponseType(204)]
[ProducesResponseType(400)]
[ProducesResponseType(404)]
[ProducesResponseType(410)]
@@ -337,7 +335,7 @@ namespace WalletWasabi.Backend.Controllers
case CcjRoundPhase.InputRegistration:
{
round.StartAliceTimeout(alice.UniqueId);
- return NoContent();
+ break;
}
case CcjRoundPhase.ConnectionConfirmation:
{
@@ -366,13 +364,15 @@ namespace WalletWasabi.Backend.Controllers
}
}
- return Ok(round.RoundHash); // Participation can be confirmed multiple times, whatever.
+ break;
}
default:
{
return Gone($"Participation can be only confirmed from InputRegistration or ConnectionConfirmation phase. Current phase: {phase}.");
}
}
+
+ return Ok(phase);
}
/// <summary>
@@ -440,10 +440,9 @@ namespace WalletWasabi.Backend.Controllers
/// <summary>
/// Bob registers his output.
/// </summary>
- /// <param name="roundHash">Hash of the round, obtained previously.</param>
- /// <returns>RoundHash if the phase is already ConnectionConfirmation.</returns>
+ /// <param name="roundId">RoundId.</param>
/// <response code="204">Output is successfully registered.</response>
- /// <response code="400">The provided roundHash or outpurRequest was malformed.</response>
+ /// <response code="400">The provided roundId or outputRequest was malformed.</response>
/// <response code="409">Output registration can only be done from OutputRegistration phase.</response>
/// <response code="410">Output registration can only be done from a Running round.</response>
/// <response code="404">If round not found.</response>
@@ -453,9 +452,9 @@ namespace WalletWasabi.Backend.Controllers
[ProducesResponseType(404)]
[ProducesResponseType(409)]
[ProducesResponseType(410)]
- public async Task<IActionResult> PostOutputAsync([FromQuery]string roundHash, [FromBody]OutputRequest request)
+ public async Task<IActionResult> PostOutputAsync([FromQuery]long roundId, [FromBody]OutputRequest request)
{
- if (string.IsNullOrWhiteSpace(roundHash)
+ if (roundId < 0
|| request is null
|| string.IsNullOrWhiteSpace(request.OutputAddress)
|| string.IsNullOrWhiteSpace(request.SignatureHex)
@@ -464,7 +463,7 @@ namespace WalletWasabi.Backend.Controllers
return BadRequest();
}
- CcjRound round = Coordinator.TryGetRound(roundHash);
+ CcjRound round = Coordinator.TryGetRound(roundId);
if (round is null)
{
return NotFound("Round not found.");

MODIFY
WalletWasabi.Backend/README.md
WalletWasabi.Backend/README.md
| --- | ---- | ---- | ---- |
| GET status | Satoshi gets various status information. | | CurrentPhase, Denomination, RegisteredPeerCount, RequiredPeerCount, ForcedRoundStartMinutesLeft, MaximumInputCountPerPeer, FeePerInputs, FeePerOutputs, CoordinatorFee, Version |
| POST inputs | Alice registers her inputs. | Inputs[(Input, Proof)], BlindedOutputHex, ChangeOutputs[] | SignedBlindedOutput, UniqueId |
-| POST confirmation | Alice must confirm her participation periodically in InputRegistration phase and confirm once in ConnectionConfirmation phase. | UniqueId | RoundHash if the phase is already ConnectionConfirmation. |
+| POST confirmation | Alice must confirm her participation periodically in InputRegistration phase and confirm once in ConnectionConfirmation phase. | UniqueId | Phase |
| POST unconfirmation | Alice can revoke her registration without penalty if the current phase is InputRegistration. | UniqueId | |
-| POST outputs | Bob registers his output. | Output, Signature, RoundHash | |
+| POST outputs | Bob registers his output. | Output, Signature, RoundId | |
| GET coinjoin | Alice asks for the final CoinJoin transaction. | UniqueId | Transaction |
| POST signatures | Alice posts her partial signatures. | UniqueId, Signatures[(Witness, Index)] | |

MODIFY
WalletWasabi.Tests/RegTests.cs
WalletWasabi.Tests/RegTests.cs
@@ -1827,8 +1827,6 @@ namespace WalletWasabi.Tests
Assert.NotEqual(Guid.Empty, aliceClient.UniqueId);
Assert.True(aliceClient.RoundId > 0);
- Assert.Null(await aliceClient.PostConfirmationAsync());
-
CcjRunningRoundState roundState = await satoshiClient.GetRoundStateAsync(aliceClient.RoundId);
Assert.Equal(CcjRoundPhase.InputRegistration, roundState.Phase);
Assert.Equal(1, roundState.RegisteredPeerCount);
@@ -1966,10 +1964,7 @@ namespace WalletWasabi.Tests
Assert.NotNull(aliceClient.BlindedOutputSignature);
Assert.NotEqual(Guid.Empty, aliceClient.UniqueId);
Assert.True(aliceClient.RoundId > 0);
-
- Assert.Null(await aliceClient.PostConfirmationAsync());
// Double the request.
- Assert.Null(await aliceClient.PostConfirmationAsync());
// badrequests
using (var response = await torClient.SendAsync(HttpMethod.Post, $"/api/v{Helpers.Constants.BackendMajorVersion}/btc/chaumiancoinjoin/confirmation"))
{
@@ -1996,7 +1991,6 @@ namespace WalletWasabi.Tests
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
Assert.Null(await response.Content.ReadAsJsonAsync<string>());
}
- Assert.Null(await aliceClient.PostConfirmationAsync());
roundConfig.ConnectionConfirmationTimeout = 60;
coordinator.UpdateRoundConfig(roundConfig);
@@ -2010,8 +2004,6 @@ namespace WalletWasabi.Tests
Assert.NotNull(aliceClient.BlindedOutputSignature);
Assert.NotEqual(Guid.Empty, aliceClient.UniqueId);
Assert.True(aliceClient.RoundId > 0);
-
- Assert.Null(await aliceClient.PostConfirmationAsync());
await aliceClient.PostUnConfirmationAsync();
using (var response = await torClient.SendAsync(HttpMethod.Post, $"/api/v{Helpers.Constants.BackendMajorVersion}/btc/chaumiancoinjoin/unconfirmation?uniqueId={aliceClient.UniqueId}&roundId={aliceClient.RoundId}"))
{
@@ -2080,9 +2072,9 @@ namespace WalletWasabi.Tests
throw new NotSupportedException("Coordinator did not sign the blinded output properly.");
}
- var roundHash = await aliceClient1.PostConfirmationAsync();
- Assert.Equal(roundHash, await aliceClient1.PostConfirmationAsync()); // Make sure it won't throw error for double confirming.
- Assert.Equal(roundHash, await aliceClient2.PostConfirmationAsync());
+ var phase = await aliceClient1.PostConfirmationAsync();
+ Assert.Equal(phase, await aliceClient1.PostConfirmationAsync()); // Make sure it won't throw error for double confirming.
+ Assert.Equal(phase, await aliceClient2.PostConfirmationAsync());
httpRequestException = await Assert.ThrowsAsync<HttpRequestException>(async () => await aliceClient2.PostConfirmationAsync());
Assert.Equal($"{HttpStatusCode.Gone.ToReasonString()}\nParticipation can be only confirmed from InputRegistration or ConnectionConfirmation phase. Current phase: OutputRegistration.", httpRequestException.Message);
@@ -2092,8 +2084,8 @@ namespace WalletWasabi.Tests
using (var bobClient1 = new BobClient(baseUri))
using (var bobClient2 = new BobClient(baseUri))
{
- await bobClient1.PostOutputAsync(roundHash, outputAddress1, unblindedSignature1);
- await bobClient2.PostOutputAsync(roundHash, outputAddress2, unblindedSignature2);
+ await bobClient1.PostOutputAsync(aliceClient1.RoundId, outputAddress1, unblindedSignature1);
+ await bobClient2.PostOutputAsync(aliceClient2.RoundId, outputAddress2, unblindedSignature2);
}
roundState = await satoshiClient.GetRoundStateAsync(aliceClient1.RoundId);
@@ -2344,23 +2336,23 @@ namespace WalletWasabi.Tests
Assert.Equal(users.Count, roundConfig.AnonymitySet);
- var confirmationRequests = new List<Task<string>>();
+ var confirmationRequests = new List<Task<CcjRoundPhase>>();
foreach (var user in users)
{
confirmationRequests.Add(user.aliceClient.PostConfirmationAsync());
}
- string roundHash = null;
+ CcjRoundPhase roundPhase = CcjRoundPhase.InputRegistration;
foreach (var request in confirmationRequests)
{
- if (roundHash is null)
+ if (roundPhase == CcjRoundPhase.InputRegistration)
{
- roundHash = await request;
+ roundPhase = await request;
}
else
{
- Assert.Equal(roundHash, await request);
+ Assert.Equal(roundPhase, await request);
}
}
@@ -2409,23 +2401,23 @@ namespace WalletWasabi.Tests
Assert.Equal(users.Count, roundConfig.AnonymitySet);
- confirmationRequests = new List<Task<string>>();
+ confirmationRequests = new List<Task<CcjRoundPhase>>();
foreach (var user in users)
{
confirmationRequests.Add(user.aliceClient.PostConfirmationAsync());
}
- roundHash = null;
+ roundPhase = CcjRoundPhase.InputRegistration;
foreach (var request in confirmationRequests)
{
- if (roundHash is null)
+ if (roundPhase == CcjRoundPhase.InputRegistration)
{
- roundHash = await request;
+ roundPhase = await request;
}
else
{
- Assert.Equal(roundHash, await request);
+ Assert.Equal(roundPhase, await request);
}
}
@@ -2552,23 +2544,23 @@ namespace WalletWasabi.Tests
Assert.Equal(users.Count, roundConfig.AnonymitySet);
- var confirmationRequests = new List<Task<string>>();
+ var confirmationRequests = new List<Task<CcjRoundPhase>>();
foreach (var user in users)
{
confirmationRequests.Add(user.aliceClient.PostConfirmationAsync());
}
- string roundHash = null;
+ CcjRoundPhase roundPhase = CcjRoundPhase.InputRegistration;
foreach (var request in confirmationRequests)
{
- if (roundHash is null)
+ if (roundPhase == CcjRoundPhase.InputRegistration)
{
- roundHash = await request;
+ roundPhase = await request;
}
else
{
- Assert.Equal(roundHash, await request);
+ Assert.Equal(roundPhase, await request);
}
}
@@ -2576,7 +2568,7 @@ namespace WalletWasabi.Tests
foreach (var user in users)
{
var bobClient = new BobClient(baseUri);
- outputRequests.Add((bobClient, bobClient.PostOutputAsync(roundHash, user.activeOutputAddress, user.unblindedSignature)));
+ outputRequests.Add((bobClient, bobClient.PostOutputAsync(roundId, user.activeOutputAddress, user.unblindedSignature)));
}
foreach (var request in outputRequests)

MODIFY
WalletWasabi/Models/ChaumianCoinJoin/CcjClientRound.cs
WalletWasabi/Models/ChaumianCoinJoin/CcjClientRound.cs
@@ -19,7 +19,10 @@ namespace WalletWasabi.Models.ChaumianCoinJoin
public byte[] UnblindedSignature { get; set; }
- public string RoundHash { get; set; }
+ /// <summary>
+ /// Connection has been confirmed in ConnectionConfirmation Phase. Used to avoid duplicate Connection Confirmation requests.
+ /// </summary>
+ public bool ConnectionFinalConfirmed { get; set; }
public bool Signed { get; set; }
public bool PostedOutput { get; set; }
@@ -37,7 +40,7 @@ namespace WalletWasabi.Models.ChaumianCoinJoin
ChangeOutputAddress = null;
ActiveOutputAddress = null;
UnblindedSignature = null;
- RoundHash = null;
+ ConnectionFinalConfirmed = false;
AliceClient?.Dispose();
AliceClient = null;
Signed = false;

MODIFY
WalletWasabi/Models/ChaumianCoinJoin/CcjRound.cs
WalletWasabi/Models/ChaumianCoinJoin/CcjRound.cs
@@ -29,8 +29,6 @@ namespace WalletWasabi.Models.ChaumianCoinJoin
public Money FeePerInputs { get; private set; }
public Money FeePerOutputs { get; private set; }
- public string RoundHash { get; private set; }
-
public Transaction UnsignedCoinJoin { get; private set; }
private string _unsignedCoinJoinHex;
@@ -156,7 +154,6 @@ namespace WalletWasabi.Models.ChaumianCoinJoin
StatusLock = new object();
Status = CcjRoundStatus.NotStarted;
- RoundHash = null;
_unsignedCoinJoinHex = null;
UnsignedCoinJoin = null;
@@ -220,8 +217,6 @@ namespace WalletWasabi.Models.ChaumianCoinJoin
return;
}
- RoundHash = NBitcoinHelpers.HashOutpoints(Alices.SelectMany(x => x.Inputs).Select(y => y.Outpoint));
-
Phase = CcjRoundPhase.ConnectionConfirmation;
}
else if (Phase == CcjRoundPhase.ConnectionConfirmation)

MODIFY
WalletWasabi/Services/CcjClient.cs
WalletWasabi/Services/CcjClient.cs
@@ -265,20 +265,16 @@ namespace WalletWasabi.Services
if (ongoingRound.State.Phase == CcjRoundPhase.ConnectionConfirmation)
{
- if (ongoingRound.RoundHash is null) // If we didn't already obtained our roundHash obtain it.
+ if (!ongoingRound.ConnectionFinalConfirmed) // If we didn't already confirmed connection in connection confirmation phase confirm it.
{
- await ObtainRoundHashAsync(ongoingRound);
+ await ongoingRound.AliceClient.PostConfirmationAsync();
+ ongoingRound.ConnectionFinalConfirmed = true;
}
}
else if (ongoingRound.State.Phase == CcjRoundPhase.OutputRegistration)
{
if (!ongoingRound.PostedOutput)
{
- if (ongoingRound.RoundHash is null)
- {
- throw new NotSupportedException("Coordinator progressed to OutputRegistration phase, even though we didn't obtain roundHash.");
- }
-
await RegisterOutputAsync(ongoingRound);
}
}
@@ -306,10 +302,6 @@ namespace WalletWasabi.Services
private Dictionary<int, WitScript> SignCoinJoin(CcjClientRound ongoingRound, Transaction unsignedCoinJoin)
{
- if (NBitcoinHelpers.HashOutpoints(unsignedCoinJoin.Inputs.Select(x => x.PrevOut)) != ongoingRound.RoundHash)
- {
- throw new NotSupportedException("Coordinator provided invalid roundHash.");
- }
Money amountBack = unsignedCoinJoin.Outputs
.Where(x => x.ScriptPubKey == ongoingRound.ActiveOutputAddress.ScriptPubKey || x.ScriptPubKey == ongoingRound.ChangeOutputAddress.ScriptPubKey)
.Select(y => y.Value)
@@ -365,27 +357,21 @@ namespace WalletWasabi.Services
{
using (var bobClient = new BobClient(CcjHostUri, TorSocks5EndPoint))
{
- await bobClient.PostOutputAsync(ongoingRound.RoundHash, ongoingRound.ActiveOutputAddress, ongoingRound.UnblindedSignature);
+ await bobClient.PostOutputAsync(ongoingRound.AliceClient.RoundId, ongoingRound.ActiveOutputAddress, ongoingRound.UnblindedSignature);
ongoingRound.PostedOutput = true;
Logger.LogInfo<AliceClient>($"Round ({ongoingRound.State.RoundId}) Bob Posted output.");
}
}
- private static async Task ObtainRoundHashAsync(CcjClientRound ongoingRound)
- {
- string roundHash = await ongoingRound.AliceClient.PostConfirmationAsync();
- ongoingRound.RoundHash = roundHash ?? throw new NotSupportedException($"Coordinator didn't gave us the expected {nameof(roundHash)}, even though it's in ConnectionConfirmation phase.");
- }
-
private async Task TryConfirmConnectionAsync(CcjClientRound inputRegistrableRound)
{
try
{
- string roundHash = await inputRegistrableRound.AliceClient.PostConfirmationAsync();
- if (!(roundHash is null)) // Then the phase went to connection confirmation.
+ CcjRoundPhase phase = await inputRegistrableRound.AliceClient.PostConfirmationAsync();
+ if (phase > CcjRoundPhase.InputRegistration) // Then the phase went to connection confirmation (probably).
{
- inputRegistrableRound.RoundHash = roundHash;
- inputRegistrableRound.State.Phase = CcjRoundPhase.ConnectionConfirmation;
+ inputRegistrableRound.ConnectionFinalConfirmed = true;
+ inputRegistrableRound.State.Phase = phase;
}
}
catch (Exception ex)

MODIFY
WalletWasabi/Services/CcjCoordinator.cs
WalletWasabi/Services/CcjCoordinator.cs
@@ -302,14 +302,6 @@ namespace WalletWasabi.Services
}
}
- public CcjRound TryGetRound(string roundHash)
- {
- using (RoundsListLock.Lock())
- {
- return Rounds.SingleOrDefault(x => x.RoundHash == roundHash);
- }
- }
-
public bool AnyRunningRoundContainsInput(OutPoint input, out List<Alice> alices)
{
using (RoundsListLock.Lock())

MODIFY
WalletWasabi/WebClients/Wasabi/ChaumianCoinJoin/AliceClient.cs
WalletWasabi/WebClients/Wasabi/ChaumianCoinJoin/AliceClient.cs
@@ -14,6 +14,7 @@ using WalletWasabi.Logging;
using WalletWasabi.Bases;
using System.Threading;
using WalletWasabi.Exceptions;
+using WalletWasabi.Models.ChaumianCoinJoin;
namespace WalletWasabi.WebClients.Wasabi.ChaumianCoinJoin
{
@@ -70,29 +71,21 @@ namespace WalletWasabi.WebClients.Wasabi.ChaumianCoinJoin
return await CreateNewAsync(network, request, baseUri, torSocks5EndPoint);
}
- /// <returns>null or roundHash</returns>
- public async Task<string> PostConfirmationAsync()
+ public async Task<CcjRoundPhase> PostConfirmationAsync()
{
using (HttpResponseMessage response = await TorClient.SendAsync(HttpMethod.Post, $"/api/v{Helpers.Constants.BackendMajorVersion}/btc/chaumiancoinjoin/confirmation?uniqueId={UniqueId}&roundId={RoundId}"))
{
- if (response.StatusCode == HttpStatusCode.NoContent)
- {
- Logger.LogInfo<AliceClient>($"Round ({RoundId}), Alice ({UniqueId}): Confirmed connection.");
- return null;
- }
-
if (response.StatusCode != HttpStatusCode.OK)
{
await response.ThrowRequestExceptionFromContentAsync();
}
- string roundHash = await response.Content.ReadAsJsonAsync<string>();
- Logger.LogInfo<AliceClient>($"Round ({RoundId}), Alice ({UniqueId}): Confirmed connection. Acquired roundHash: {roundHash}.");
- return roundHash;
+ CcjRoundPhase phase = await response.Content.ReadAsJsonAsync<CcjRoundPhase>();
+ Logger.LogInfo<AliceClient>($"Round ({RoundId}), Alice ({UniqueId}): Confirmed connection. Phase: {phase}.");
+ return phase;
}
}
- /// <returns>null or roundHash</returns>
public async Task PostUnConfirmationAsync()
{
using (var cts = new CancellationTokenSource(TimeSpan.FromSeconds(3)))

MODIFY
WalletWasabi/WebClients/Wasabi/ChaumianCoinJoin/BobClient.cs
WalletWasabi/WebClients/Wasabi/ChaumianCoinJoin/BobClient.cs
@@ -15,10 +15,10 @@ namespace WalletWasabi.WebClients.Wasabi.ChaumianCoinJoin
{
}
- public async Task PostOutputAsync(string roundHash, BitcoinAddress activeOutputAddress, byte[] unblindedSignature)
+ public async Task PostOutputAsync(long roundId, BitcoinAddress activeOutputAddress, byte[] unblindedSignature)
{
var request = new OutputRequest { OutputAddress = activeOutputAddress.ToString(), SignatureHex = ByteHelpers.ToHex(unblindedSignature) };
- using (var response = await TorClient.SendAsync(HttpMethod.Post, $"/api/v{Helpers.Constants.BackendMajorVersion}/btc/chaumiancoinjoin/output?roundHash={roundHash}", request.ToHttpStringContent()))
+ using (var response = await TorClient.SendAsync(HttpMethod.Post, $"/api/v{Helpers.Constants.BackendMajorVersion}/btc/chaumiancoinjoin/output?roundId={roundId}", request.ToHttpStringContent()))
{
if (response.StatusCode != HttpStatusCode.NoContent)
{

Remove RoundHash