Log late requests
MODIFY
WalletWasabi.Backend/Controllers/ChaumianCoinJoinController.cs
WalletWasabi.Backend/Controllers/ChaumianCoinJoinController.cs
@@ -10,6 +10,7 @@ using Nito.AsyncEx;
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
+using System.IO;
using System.Linq;
using System.Net;
using System.Threading.Tasks;
@@ -361,7 +362,7 @@ namespace WalletWasabi.Backend.Controllers
return BadRequest();
}
- (CcjRound round, Alice alice) = GetRunningRoundAndAliceOrFailureResponse(roundId, uniqueId, out IActionResult returnFailureResponse);
+ (CcjRound round, Alice alice) = GetRunningRoundAndAliceOrFailureResponse(roundId, uniqueId, CcjRoundPhase.ConnectionConfirmation, out IActionResult returnFailureResponse);
if (returnFailureResponse != null)
{
return returnFailureResponse;
@@ -407,6 +408,7 @@ namespace WalletWasabi.Backend.Controllers
}
default:
{
+ TryLogLateRequest(roundId, CcjRoundPhase.ConnectionConfirmation);
return Gone($"Participation can be only confirmed from InputRegistration or ConnectionConfirmation phase. Current phase: {phase}.");
}
}
@@ -503,17 +505,20 @@ namespace WalletWasabi.Backend.Controllers
CcjRound round = Coordinator.TryGetRound(roundId);
if (round is null)
{
+ TryLogLateRequest(roundId, CcjRoundPhase.OutputRegistration);
return NotFound("Round not found.");
}
if (round.Status != CcjRoundStatus.Running)
{
+ TryLogLateRequest(roundId, CcjRoundPhase.OutputRegistration);
return Gone("Round is not running.");
}
CcjRoundPhase phase = round.Phase;
if (phase != CcjRoundPhase.OutputRegistration)
{
+ TryLogLateRequest(roundId, CcjRoundPhase.OutputRegistration);
return Conflict($"Output registration can only be done from OutputRegistration phase. Current phase: {phase}.");
}
@@ -597,7 +602,7 @@ namespace WalletWasabi.Backend.Controllers
return BadRequest();
}
- (CcjRound round, Alice alice) = GetRunningRoundAndAliceOrFailureResponse(roundId, uniqueId, out IActionResult returnFailureResponse);
+ (CcjRound round, Alice alice) = GetRunningRoundAndAliceOrFailureResponse(roundId, uniqueId, CcjRoundPhase.Signing, out IActionResult returnFailureResponse);
if (returnFailureResponse != null)
{
return returnFailureResponse;
@@ -612,6 +617,7 @@ namespace WalletWasabi.Backend.Controllers
}
default:
{
+ TryLogLateRequest(roundId, CcjRoundPhase.Signing);
return Conflict($"CoinJoin can only be requested from Signing phase. Current phase: {phase}.");
}
}
@@ -647,7 +653,7 @@ namespace WalletWasabi.Backend.Controllers
return BadRequest();
}
- (CcjRound round, Alice alice) = GetRunningRoundAndAliceOrFailureResponse(roundId, uniqueId, out IActionResult returnFailureResponse);
+ (CcjRound round, Alice alice) = GetRunningRoundAndAliceOrFailureResponse(roundId, uniqueId, CcjRoundPhase.Signing, out IActionResult returnFailureResponse);
if (returnFailureResponse != null)
{
return returnFailureResponse;
@@ -718,6 +724,7 @@ namespace WalletWasabi.Backend.Controllers
}
default:
{
+ TryLogLateRequest(roundId, CcjRoundPhase.Signing);
return Conflict($"CoinJoin can only be requested from Signing phase. Current phase: {phase}.");
}
}
@@ -750,7 +757,7 @@ namespace WalletWasabi.Backend.Controllers
return aliceGuid;
}
- private (CcjRound round, Alice alice) GetRunningRoundAndAliceOrFailureResponse(long roundId, string uniqueId, out IActionResult returnFailureResponse)
+ private (CcjRound round, Alice alice) GetRunningRoundAndAliceOrFailureResponse(long roundId, string uniqueId, CcjRoundPhase desiredPhase, out IActionResult returnFailureResponse)
{
returnFailureResponse = null;
@@ -766,6 +773,7 @@ namespace WalletWasabi.Backend.Controllers
if (round is null)
{
+ TryLogLateRequest(roundId, desiredPhase);
returnFailureResponse = NotFound("Round not found.");
return (null, null);
}
@@ -779,12 +787,29 @@ namespace WalletWasabi.Backend.Controllers
if (round.Status != CcjRoundStatus.Running)
{
+ TryLogLateRequest(roundId, desiredPhase);
returnFailureResponse = Gone("Round is not running.");
}
return (round, alice);
}
+ private static void TryLogLateRequest(long roundId, CcjRoundPhase desiredPhase)
+ {
+ try
+ {
+ DateTimeOffset ended = CcjRound.PhaseTimeoutLog.TryGet((roundId, desiredPhase));
+ if (ended != default)
+ {
+ Logger.LogInfo<ChaumianCoinJoinController>($"{DateTime.UtcNow.ToLocalTime():yyyy-MM-dd HH:mm:ss} {desiredPhase} {(int)(DateTimeOffset.UtcNow - ended).TotalSeconds} seconds late.");
+ }
+ }
+ catch (Exception ex)
+ {
+ Logger.LogDebug<ChaumianCoinJoinController>(ex);
+ }
+ }
+
/// <summary>
/// 409
/// </summary>

MODIFY
WalletWasabi/Models/ChaumianCoinJoin/CcjRound.cs
WalletWasabi/Models/ChaumianCoinJoin/CcjRound.cs
@@ -4,6 +4,7 @@ using NBitcoin.RPC;
using Newtonsoft.Json.Linq;
using Nito.AsyncEx;
using System;
+using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
@@ -190,6 +191,8 @@ namespace WalletWasabi.Models.ChaumianCoinJoin
}
}
+ public static ConcurrentDictionary<(long roundId, CcjRoundPhase phase), DateTimeOffset> PhaseTimeoutLog { get; } = new ConcurrentDictionary<(long roundId, CcjRoundPhase phase), DateTimeOffset>();
+
public async Task ExecuteNextPhaseAsync(CcjRoundPhase expectedPhase, Money feePerInputs = null, Money feePerOutputs = null)
{
using (await RoundSynchronizerLock.LockAsync())
@@ -373,7 +376,7 @@ namespace WalletWasabi.Models.ChaumianCoinJoin
Phase = CcjRoundPhase.Signing;
}
- else
+ else // Phase == CcjRoundPhase.Signing
{
return;
}
@@ -423,6 +426,7 @@ namespace WalletWasabi.Models.ChaumianCoinJoin
}
if (executeRunAbortion)
{
+ PhaseTimeoutLog.TryAdd((RoundId, Phase), DateTimeOffset.UtcNow);
string timedOutLogString = $"Round ({RoundId}): {expectedPhase.ToString()} timed out after {timeout.TotalSeconds} seconds.";
if (expectedPhase == CcjRoundPhase.ConnectionConfirmation)

Log late requests