Make sure Alice registers to the right round
MODIFY
WalletWasabi.Backend/Controllers/ChaumianCoinJoinController.cs
WalletWasabi.Backend/Controllers/ChaumianCoinJoinController.cs
@@ -98,15 +98,15 @@ namespace WalletWasabi.Backend.Controllers
/// </remarks>
/// <response code="200">BlindedOutputSignature, UniqueId, RoundId</response>
/// <response code="400">If request is invalid.</response>
- /// <response code="503">If the round status changed while fulfilling the request.</response>
+ /// <response code="404">Round not found or it is not in InputRegistration anymore.</response>
[HttpPost("inputs")]
[ProducesResponseType(200)]
[ProducesResponseType(400)]
- [ProducesResponseType(503)]
+ [ProducesResponseType(404)]
public async Task<IActionResult> PostInputsAsync([FromBody, Required]InputsRequest request)
{
// Validate request.
- if (!ModelState.IsValid)
+ if (request.RoundId <= 0 || !ModelState.IsValid)
{
return BadRequest("Invalid request.");
}
@@ -118,10 +118,11 @@ namespace WalletWasabi.Backend.Controllers
using (await InputsLock.LockAsync())
{
- CcjRound round = Coordinator.GetCurrentInputRegisterableRoundOrDefault();
- if (round == default)
+ CcjRound round = Coordinator.TryGetRound(request.RoundId);
+
+ if (round is null || round.Phase != CcjRoundPhase.InputRegistration)
{
- return StatusCode(StatusCodes.Status503ServiceUnavailable, "No input registrable rounds available. Try again later.");
+ return NotFound("No such running round in InputRegistration. Try another round.");
}
// Do more checks.

MODIFY
WalletWasabi.Tests/RegTests.cs
WalletWasabi.Tests/RegTests.cs
@@ -2380,7 +2380,7 @@ namespace WalletWasabi.Tests
InputProofModel inputProof = new InputProofModel { Input = input.ToTxoRef(), Proof = inputKey.SignCompact(blindedOutputScriptsHash) };
InputProofModel[] inputsProofs = new InputProofModel[] { inputProof };
registerRequests.Add((changeOutputAddress, blinded, inputsProofs));
- aliceClientBackup = await AliceClient.CreateNewAsync(new[] { activeOutputAddress }, new[] { round.MixingLevels.GetBaseLevel().SchnorrKey.SchnorrPubKey }, new[] { requester }, network, changeOutputAddress, new[] { blinded }, inputsProofs, baseUri);
+ aliceClientBackup = await AliceClient.CreateNewAsync(round.RoundId, new[] { activeOutputAddress }, new[] { round.MixingLevels.GetBaseLevel().SchnorrKey.SchnorrPubKey }, new[] { requester }, network, changeOutputAddress, new[] { blinded }, inputsProofs, baseUri);
}
await WaitForTimeoutAsync(baseUri);
@@ -2392,7 +2392,7 @@ namespace WalletWasabi.Tests
foreach (var registerRequest in registerRequests)
{
- await AliceClient.CreateNewAsync(aliceClientBackup.RegisteredAddresses, aliceClientBackup.SchnorrPubKeys, aliceClientBackup.Requesters, network, registerRequest.changeOutputAddress, new[] { registerRequest.blindedData }, registerRequest.inputsProofs, baseUri);
+ await AliceClient.CreateNewAsync(aliceClientBackup.RoundId, aliceClientBackup.RegisteredAddresses, aliceClientBackup.SchnorrPubKeys, aliceClientBackup.Requesters, network, registerRequest.changeOutputAddress, new[] { registerRequest.blindedData }, registerRequest.inputsProofs, baseUri);
}
await WaitForTimeoutAsync(baseUri);
@@ -2495,7 +2495,7 @@ namespace WalletWasabi.Tests
foreach (var user in inputRegistrationUsers)
{
- aliceClients.Add(AliceClient.CreateNewAsync(new[] { user.activeOutputAddress }, new[] { round.MixingLevels.GetBaseLevel().SchnorrKey.SchnorrPubKey }, new[] { user.requester }, network, user.changeOutputAddress, new[] { user.blinded }, user.inputProofModels, baseUri));
+ aliceClients.Add(AliceClient.CreateNewAsync(round.RoundId, new[] { user.activeOutputAddress }, new[] { round.MixingLevels.GetBaseLevel().SchnorrKey.SchnorrPubKey }, new[] { user.requester }, network, user.changeOutputAddress, new[] { user.blinded }, user.inputProofModels, baseUri));
}
long roundId = 0;
@@ -2567,7 +2567,7 @@ namespace WalletWasabi.Tests
round = coordinator.GetCurrentInputRegisterableRoundOrDefault();
foreach (var user in inputRegistrationUsers)
{
- aliceClients.Add(AliceClient.CreateNewAsync(new[] { user.activeOutputAddress }, new[] { round.MixingLevels.GetBaseLevel().SchnorrKey.SchnorrPubKey }, new[] { user.requester }, network, user.changeOutputAddress, new[] { user.blinded }, user.inputProofModels, baseUri));
+ aliceClients.Add(AliceClient.CreateNewAsync(round.RoundId, new[] { user.activeOutputAddress }, new[] { round.MixingLevels.GetBaseLevel().SchnorrKey.SchnorrPubKey }, new[] { user.requester }, network, user.changeOutputAddress, new[] { user.blinded }, user.inputProofModels, baseUri));
}
roundId = 0;

MODIFY
WalletWasabi/Backend/Models/Requests/InputsRequest.cs
WalletWasabi/Backend/Models/Requests/InputsRequest.cs
@@ -10,6 +10,9 @@ namespace WalletWasabi.Backend.Models.Requests
{
public class InputsRequest
{
+ [Required]
+ public long RoundId { get; set; }
+
[Required, MinLength(1)]
public IEnumerable<InputProofModel> Inputs { get; set; }

MODIFY
WalletWasabi/Services/CcjClient.cs
WalletWasabi/Services/CcjClient.cs
@@ -496,7 +496,7 @@ namespace WalletWasabi.Services
AliceClient aliceClient = null;
try
{
- aliceClient = await AliceClient.CreateNewAsync(registeredAddresses, schnorrPubKeys, requesters, Network, outputAddresses.change.GetP2wpkhAddress(Network), blindedOutputScriptHashes, inputProofs, CcjHostUri, TorSocks5EndPoint);
+ aliceClient = await AliceClient.CreateNewAsync(inputRegistrableRound.RoundId, registeredAddresses, schnorrPubKeys, requesters, Network, outputAddresses.change.GetP2wpkhAddress(Network), blindedOutputScriptHashes, inputProofs, CcjHostUri, TorSocks5EndPoint);
}
catch (HttpRequestException ex) when (ex.Message.Contains("Input is banned", StringComparison.InvariantCultureIgnoreCase))
{
@@ -513,6 +513,7 @@ namespace WalletWasabi.Services
Logger.LogWarning<CcjClient>(ex.Message.Split('\n')[1]);
await DequeueCoinsFromMixNoLockAsync(coinReference);
+ aliceClient?.Dispose();
return;
}
catch (HttpRequestException ex) when (ex.Message.Contains("Provided input is not unspent", StringComparison.InvariantCultureIgnoreCase))
@@ -528,6 +529,13 @@ namespace WalletWasabi.Services
Logger.LogWarning<CcjClient>(ex.Message.Split('\n')[1]);
await DequeueCoinsFromMixNoLockAsync(coinReference);
+ aliceClient?.Dispose();
+ return;
+ }
+ catch (HttpRequestException ex) when (ex.Message.Contains("No such running round in InputRegistration.", StringComparison.InvariantCultureIgnoreCase))
+ {
+ Logger.LogInfo<CcjClient>("Client tried to register a round that isn't in InputRegistration anymore. Trying again later.");
+ aliceClient?.Dispose();
return;
}

MODIFY
WalletWasabi/WebClients/Wasabi/ChaumianCoinJoin/AliceClient.cs
WalletWasabi/WebClients/Wasabi/ChaumianCoinJoin/AliceClient.cs
@@ -23,8 +23,9 @@ namespace WalletWasabi.WebClients.Wasabi.ChaumianCoinJoin
{
public class AliceClient : TorDisposableBase
{
- public long RoundId { get; private set; }
public Guid UniqueId { get; private set; }
+
+ public long RoundId { get; }
public Network Network { get; }
public BitcoinAddress[] RegisteredAddresses { get; }
@@ -33,6 +34,7 @@ namespace WalletWasabi.WebClients.Wasabi.ChaumianCoinJoin
/// <inheritdoc/>
private AliceClient(
+ long roundId,
IEnumerable<BitcoinAddress> registeredAddresses,
IEnumerable<SchnorrPubKey> schnorrPubKeys,
IEnumerable<Requester> requesters,
@@ -40,6 +42,7 @@ namespace WalletWasabi.WebClients.Wasabi.ChaumianCoinJoin
Uri baseUri,
IPEndPoint torSocks5EndPoint = null) : base(baseUri, torSocks5EndPoint)
{
+ RoundId = roundId;
RegisteredAddresses = registeredAddresses.ToArray();
SchnorrPubKeys = schnorrPubKeys.ToArray();
Requesters = requesters.ToArray();
@@ -47,6 +50,7 @@ namespace WalletWasabi.WebClients.Wasabi.ChaumianCoinJoin
}
public static async Task<AliceClient> CreateNewAsync(
+ long roundId,
IEnumerable<BitcoinAddress> registeredAddresses,
IEnumerable<SchnorrPubKey> schnorrPubKeys,
IEnumerable<Requester> requesters,
@@ -55,7 +59,7 @@ namespace WalletWasabi.WebClients.Wasabi.ChaumianCoinJoin
Uri baseUri,
IPEndPoint torSocks5EndPoint = null)
{
- AliceClient client = new AliceClient(registeredAddresses, schnorrPubKeys, requesters, network, baseUri, torSocks5EndPoint);
+ AliceClient client = new AliceClient(roundId, registeredAddresses, schnorrPubKeys, requesters, network, baseUri, torSocks5EndPoint);
try
{
using (HttpResponseMessage response = await client.TorClient.SendAsync(HttpMethod.Post, $"/api/v{Helpers.Constants.BackendMajorVersion}/btc/chaumiancoinjoin/inputs/", request.ToHttpStringContent()))
@@ -67,7 +71,11 @@ namespace WalletWasabi.WebClients.Wasabi.ChaumianCoinJoin
var inputsResponse = await response.Content.ReadAsJsonAsync<InputsResponse>();
- client.RoundId = inputsResponse.RoundId;
+ if (inputsResponse.RoundId != roundId) // This should never happen. If it does, that's a bug in the coordinator.
+ {
+ throw new NotSupportedException($"Coordinator assigned us to the wrong round: {inputsResponse.RoundId}. Requested round: {roundId}.");
+ }
+
client.UniqueId = inputsResponse.UniqueId;
Logger.LogInfo<AliceClient>($"Round ({client.RoundId}), Alice ({client.UniqueId}): Registered {request.Inputs.Count()} inputs.");
@@ -82,6 +90,7 @@ namespace WalletWasabi.WebClients.Wasabi.ChaumianCoinJoin
}
public static async Task<AliceClient> CreateNewAsync(
+ long roundId,
IEnumerable<BitcoinAddress> registeredAddresses,
IEnumerable<SchnorrPubKey> schnorrPubKeys,
IEnumerable<Requester> requesters,
@@ -94,11 +103,12 @@ namespace WalletWasabi.WebClients.Wasabi.ChaumianCoinJoin
{
var request = new InputsRequest
{
+ RoundId = roundId,
BlindedOutputScripts = blindedOutputScriptHashes,
ChangeOutputAddress = changeOutput,
Inputs = inputs
};
- return await CreateNewAsync(registeredAddresses, schnorrPubKeys, requesters, network, request, baseUri, torSocks5EndPoint);
+ return await CreateNewAsync(roundId, registeredAddresses, schnorrPubKeys, requesters, network, request, baseUri, torSocks5EndPoint);
}
public async Task<(CcjRoundPhase currentPhase, IEnumerable<(BitcoinAddress output, UnblindedSignature signature, int level)> activeOutputs)> PostConfirmationAsync()

Make sure Alice registers to the right round