Retry if round aborted
MODIFY
HiddenWallet.ChaumianTumbler/TumblerStateMachine.cs
HiddenWallet.ChaumianTumbler/TumblerStateMachine.cs
@@ -59,6 +59,10 @@ namespace HiddenWallet.ChaumianTumbler
{
AcceptRequest = true;
var broadcast = new PhaseChangeBroadcast { NewPhase = Phase.ToString(), Message = "" };
+ if (FallBackRound && Phase == TumblerPhase.InputRegistration)
+ {
+ broadcast = new PhaseChangeBroadcast { NewPhase = "FallBack" + Phase.ToString(), Message = "" };
+ }
await _broadcaster.BroadcastAsync(broadcast);
Console.WriteLine($"NEW PHASE: {Phase}");
}

MODIFY
HiddenWallet.Daemon/Controllers/TumblerController.cs
HiddenWallet.Daemon/Controllers/TumblerController.cs
@@ -104,7 +104,18 @@ namespace HiddenWallet.Daemon.Controllers
BitcoinAddress activeOutput = unusedOutputs.RandomElement().GetDestinationAddress(Global.WalletWrapper.Network); // TODO: this is sub-optimal, it'd be better to not which had been already registered and not reregister it
BitcoinWitPubKeyAddress bech32 = new BitcoinWitPubKeyAddress(activeOutput.ToString(), Global.WalletWrapper.Network);
- uint256 txid = await Global.WalletWrapper.WalletJob.CoinJoinService.TumbleAsync(fromAccount, bech32, CancelMixSource.Token);
+ uint256 txid = null;
+ try
+ {
+ txid = await Global.WalletWrapper.WalletJob.CoinJoinService.TumbleAsync(fromAccount, bech32, CancelMixSource.Token);
+ }
+ catch (InvalidOperationException e) when (e.Message == "Round aborted")
+ {
+ // register for the next round
+ i--;
+ continue;
+ }
+
if (txid == null)
{
return new ObjectResult(new FailureResponse { Message = "Either the coordinator failed to propagate the latest transaction or it did not arrive to our mempool", Details = "Successful mixes:" + Environment.NewLine + string.Join(Environment.NewLine, txIds.Select(a => a.ToString())) });

MODIFY
HiddenWallet.Documentation/TumblerApiSpecs.md
HiddenWallet.Documentation/TumblerApiSpecs.md
| Type | Response |
|------| -----------|
-| new-phase | Phase: InputRegistration/ ConnectionConfirmation/ OutputRegistration/ Signing |
+| new-phase | Phase: FallBackInputRegistration, InputRegistration/ ConnectionConfirmation/ OutputRegistration/ Signing |
| new-registration | NumberOfPeers |

MODIFY
HiddenWallet.FullSpvWallet/ChaumianCoinJoin/CoinJoinService.cs
HiddenWallet.FullSpvWallet/ChaumianCoinJoin/CoinJoinService.cs
@@ -39,6 +39,7 @@ namespace HiddenWallet.FullSpvWallet.ChaumianCoinJoin
public volatile Exception TumblingException;
public volatile bool CompletedLastPhase;
public volatile Transaction CoinJoin;
+ private volatile bool _abortOngoingRound;
public Money Denomination => Money.Parse(StatusResponse?.Denomination ?? "0");
public StatusResponse StatusResponse { get; set; }
@@ -89,6 +90,11 @@ namespace HiddenWallet.FullSpvWallet.ChaumianCoinJoin
var jObject = JObject.Parse(data);
var phaseString = jObject.Value<string>("NewPhase");
+ if(phaseString.StartsWith("FallBack", StringComparison.OrdinalIgnoreCase))
+ {
+ _abortOngoingRound = true;
+ }
+
Debug.WriteLine($"New Phase: {phaseString}");
var phase = TumblerPhaseHelpers.GetTumblerPhase(phaseString);
Phase = phase;
@@ -172,7 +178,7 @@ namespace HiddenWallet.FullSpvWallet.ChaumianCoinJoin
if (TumblerConnection == null)
{
- throw new HttpRequestException("Tumbler is offline");
+ throw new HttpRequestException("Coordinator is offline");
}
}
@@ -184,7 +190,7 @@ namespace HiddenWallet.FullSpvWallet.ChaumianCoinJoin
// wait for input registration
while (Phase != TumblerPhase.InputRegistration)
{
- if (TumblerConnection == null) throw new HttpRequestException("Server went offline");
+ if (TumblerConnection == null) throw new HttpRequestException("Coordinator went offline");
status = null;
await Task.Delay(100, cancel);
}
@@ -193,10 +199,15 @@ namespace HiddenWallet.FullSpvWallet.ChaumianCoinJoin
await ExecutePhaseAsync(TumblerPhase.InputRegistration, status, cancel);
// wait while tumbling is in process
+ _abortOngoingRound = false;
while (TumblingInProcess)
{
if (TumblerConnection == null) throw new HttpRequestException("Server went offline");
if (Phase == TumblerPhase.InputRegistration) cancel.ThrowIfCancellationRequested();
+ if (_abortOngoingRound)
+ {
+ throw new InvalidOperationException("Round aborted"); // keep it inact, will check later
+ }
await Task.Delay(100);
}

MODIFY
HiddenWallet/ChaumianCoinJoin/TumblerPhase.cs
HiddenWallet/ChaumianCoinJoin/TumblerPhase.cs
@@ -18,6 +18,8 @@ namespace HiddenWallet.ChaumianCoinJoin
{
if (phase == null) throw new ArgumentNullException(nameof(phase));
+ if (phase == "FallBack" + phase.ToString()) return TumblerPhase.InputRegistration;
+
foreach (TumblerPhase p in Enum.GetValues(typeof(TumblerPhase)))
{
if (phase.Equals(p.ToString(), StringComparison.OrdinalIgnoreCase))

Retry if round aborted