Step TransactionSigning phase
MODIFY
WalletWasabi/WabiSabi/Backend/PostRequests/InputRegistrationHandler.cs
WalletWasabi/WabiSabi/Backend/PostRequests/InputRegistrationHandler.cs
@@ -71,7 +71,8 @@ namespace WalletWasabi.WabiSabi.Backend.PostRequests
IDictionary<Guid, Round> rounds,
Network network,
uint maxInputCountByRound,
- TimeSpan inputRegistrationTimeout)
+ TimeSpan inputRegistrationTimeout,
+ TimeSpan connectionConfirmationTimeout)
{
if (!rounds.TryGetValue(roundId, out var round))
{
@@ -131,7 +132,7 @@ namespace WalletWasabi.WabiSabi.Backend.PostRequests
RemoveDuplicateAlices(rounds, alice);
- alice.SetDeadlineRelativeTo(round.ConnectionConfirmationTimeout);
+ alice.SetDeadlineRelativeTo(connectionConfirmationTimeout);
round.Alices.Add(alice);
return new(alice.Id, amountCredentialResponse, weightCredentialResponse);

MODIFY
WalletWasabi/WabiSabi/Backend/Rounds/Arena.cs
WalletWasabi/WabiSabi/Backend/Rounds/Arena.cs
@@ -46,12 +46,16 @@ namespace WalletWasabi.WabiSabi.Backend.Rounds
// Remove timed out alices.
TimeoutAlices();
+ await StepTransactionSigningPhaseAsync().ConfigureAwait(false);
+
StepOutputRegistrationPhase();
StepConnectionConfirmationPhase();
StepInputRegistrationPhase();
+ cancel.ThrowIfCancellationRequested();
+
// Ensure there's at least one non-blame round in input registration.
await CreateRoundsAsync().ConfigureAwait(false);
}
@@ -148,6 +152,61 @@ namespace WalletWasabi.WabiSabi.Backend.Rounds
}
}
+ private async Task StepTransactionSigningPhaseAsync()
+ {
+ foreach (var round in Rounds.Values.Where(x => x.Phase == Phase.TransactionSigning))
+ {
+ var coinjoin = round.Coinjoin;
+ var isFullySigned = coinjoin.Inputs.All(x => x.HasWitScript());
+ if (isFullySigned)
+ {
+ try
+ {
+ await Rpc.SendRawTransactionAsync(coinjoin).ConfigureAwait(false);
+ round.SetPhase(Phase.TransactionBroadcasting);
+ }
+ catch (Exception ex)
+ {
+ Logger.LogWarning(ex);
+ await FailTransactionSigningPhaseAsync(round).ConfigureAwait(false);
+ }
+ }
+ else if (round.TransactionSigningStart + Config.TransactionSigningTimeout < DateTimeOffset.UtcNow)
+ {
+ await FailTransactionSigningPhaseAsync(round).ConfigureAwait(false);
+ }
+ }
+ }
+
+ private async Task FailTransactionSigningPhaseAsync(Round round)
+ {
+ var alicesWhoDidntSign = round
+ .Alices
+ .Where(x => !x
+ .Coins
+ .Select(x => x.Outpoint)
+ .All(y => round.Coinjoin.Inputs.Select(x => x.PrevOut).Contains(y)))
+ .ToHashSet();
+
+ // If we found someone to ban don't check for spent inputs
+ // because that's expensive with Bitcoin Core's RPC.
+ var alicesToBan = alicesWhoDidntSign;
+ if (!alicesWhoDidntSign.Any())
+ {
+ await foreach (var alice in GetSpentAlicesAsync(round.Alices).ConfigureAwait(false))
+ {
+ alicesToBan.Add(alice);
+ }
+ }
+
+ foreach (var alice in alicesToBan)
+ {
+ Prison.Note(alice, round.Id);
+ }
+
+ Rounds.Remove(round.Id);
+ }
+
private async Task CreateRoundsAsync()
{
if (!Rounds.Values.Any(x => !x.IsBlameRound && x.Phase == Phase.InputRegistration))
@@ -172,6 +231,31 @@ namespace WalletWasabi.WabiSabi.Backend.Rounds
}
}
+ private async IAsyncEnumerable<Alice> GetSpentAlicesAsync(IEnumerable<Alice> alices)
+ {
+ foreach (var alice in alices)
+ {
+ foreach (var input in alice.Coins.Select(x => x.Outpoint))
+ {
+ GetTxOutResponse? resp = null;
+ try
+ {
+ resp = await Rpc.GetTxOutAsync(input.Hash, (int)input.N, includeMempool: true).ConfigureAwait(false);
+ }
+ catch (Exception ex)
+ {
+ Logger.LogError(ex);
+ }
+
+ if (resp is null)
+ {
+ yield return alice;
+ break;
+ }
+ }
+ }
+ }
+
public async Task<InputsRegistrationResponse> RegisterInputAsync(
Guid roundId,
IDictionary<Coin, byte[]> coinRoundSignaturePairs,
@@ -188,7 +272,8 @@ namespace WalletWasabi.WabiSabi.Backend.Rounds
Rounds,
Network,
Config.MaxInputCountByRound,
- Config.InputRegistrationTimeout);
+ Config.InputRegistrationTimeout,
+ Config.ConnectionConfirmationTimeout);
}
}
@@ -240,7 +325,7 @@ namespace WalletWasabi.WabiSabi.Backend.Rounds
if (round.Phase == Phase.InputRegistration)
{
- alice.SetDeadlineRelativeTo(round.ConnectionConfirmationTimeout);
+ alice.SetDeadlineRelativeTo(Config.ConnectionConfirmationTimeout);
return new(
amountZeroCredentialResponse,
weightZeroCredentialResponse);

MODIFY
WalletWasabi/WabiSabi/Backend/Rounds/Phase.cs
WalletWasabi/WabiSabi/Backend/Rounds/Phase.cs
@@ -11,6 +11,7 @@ namespace WalletWasabi.WabiSabi.Backend.Rounds
InputRegistration,
ConnectionConfirmation,
OutputRegistration,
- TransactionSigning
+ TransactionSigning,
+ TransactionBroadcasting
}
}

MODIFY
WalletWasabi/WabiSabi/Backend/Rounds/Round.cs
WalletWasabi/WabiSabi/Backend/Rounds/Round.cs
@@ -49,9 +49,6 @@ namespace WalletWasabi.WabiSabi.Backend.Rounds
public Money MinRegistrableAmount => RoundParameters.MinRegistrableAmount;
public Money MaxRegistrableAmount => RoundParameters.MaxRegistrableAmount;
public uint RegistrableWeightCredentials => RoundParameters.RegistrableWeightCredentials;
- public TimeSpan ConnectionConfirmationTimeout => RoundParameters.ConnectionConfirmationTimeout;
- public TimeSpan OutputRegistrationTimeout => RoundParameters.OutputRegistrationTimeout;
- public TimeSpan TransactionSigningTimeout => RoundParameters.TransactionSigningTimeout;
public FeeRate FeeRate => RoundParameters.FeeRate;
public WasabiRandom Random => RoundParameters.Random;
public CredentialIssuer AmountCredentialIssuer { get; }
@@ -73,6 +70,7 @@ namespace WalletWasabi.WabiSabi.Backend.Rounds
public DateTimeOffset ConnectionConfirmationStart { get; private set; }
public DateTimeOffset OutputRegistrationStart { get; private set; }
public DateTimeOffset TransactionSigningStart { get; private set; }
+ public DateTimeOffset TransactionBroadcastingStart { get; private set; }
public void SetPhase(Phase phase)
{
@@ -90,6 +88,10 @@ namespace WalletWasabi.WabiSabi.Backend.Rounds
{
TransactionSigningStart = DateTimeOffset.UtcNow;
}
+ else if (phase == Phase.TransactionBroadcasting)
+ {
+ TransactionBroadcastingStart = DateTimeOffset.UtcNow;
+ }
}
public bool IsInputRegistrationEnded(uint maxInputCount, TimeSpan inputRegistrationTimeout)

MODIFY
WalletWasabi/WabiSabi/Backend/Rounds/RoundParameters.cs
WalletWasabi/WabiSabi/Backend/Rounds/RoundParameters.cs
@@ -23,9 +23,6 @@ namespace WalletWasabi.WabiSabi.Backend.Rounds
MinRegistrableAmount = wabiSabiConfig.MinRegistrableAmount;
MaxRegistrableAmount = wabiSabiConfig.MaxRegistrableAmount;
RegistrableWeightCredentials = wabiSabiConfig.RegistrableWeightCredentials;
- ConnectionConfirmationTimeout = wabiSabiConfig.ConnectionConfirmationTimeout;
- OutputRegistrationTimeout = wabiSabiConfig.OutputRegistrationTimeout;
- TransactionSigningTimeout = wabiSabiConfig.TransactionSigningTimeout;
}
public WasabiRandom Random { get; }
@@ -35,8 +32,5 @@ namespace WalletWasabi.WabiSabi.Backend.Rounds
public Money MinRegistrableAmount { get; }
public Money MaxRegistrableAmount { get; }
public uint RegistrableWeightCredentials { get; }
- public TimeSpan ConnectionConfirmationTimeout { get; }
- public TimeSpan OutputRegistrationTimeout { get; }
- public TimeSpan TransactionSigningTimeout { get; }
}
}

Step TransactionSigning phase