Implement blame round creation
MODIFY
WalletWasabi.Tests/Helpers/WabiSabiFactory.cs
WalletWasabi.Tests/Helpers/WabiSabiFactory.cs
@@ -316,5 +316,11 @@ namespace WalletWasabi.Tests.Helpers
return ret;
}
+
+ public static Round CreateBlameRound(Round round, WabiSabiConfig cfg)
+ {
+ RoundParameters parameters = new(cfg, round.Network, round.Random, round.FeeRate, blameOf: round);
+ return new(parameters);
+ }
}
}

MODIFY
WalletWasabi.Tests/UnitTests/WabiSabi/Backend/RegisterInputTests.cs
WalletWasabi.Tests/UnitTests/WabiSabi/Backend/RegisterInputTests.cs
@@ -203,9 +203,9 @@ namespace WalletWasabi.Tests.UnitTests.WabiSabi.Backend
[Fact]
public async Task InputRegistrationTimedoutAsync()
{
- WabiSabiConfig cfg = new();
+ WabiSabiConfig cfg = new() { InputRegistrationTimeout = TimeSpan.Zero };
var round = WabiSabiFactory.CreateRound(cfg);
- using Arena arena = await WabiSabiFactory.CreateAndStartArenaAsync(cfg, round);
+ using Arena arena = await WabiSabiFactory.CreateAndStartArenaAsync(cfg);
MockRpcClient rpc = new();
using Key key = new();
@@ -218,9 +218,9 @@ namespace WalletWasabi.Tests.UnitTests.WabiSabi.Backend
var req = WabiSabiFactory.CreateInputsRegistrationRequest(key, round);
await using PostRequestHandler handler = new(cfg, new Prison(), arena, rpc);
-
await arena.TriggerAndWaitRoundAsync(TimeSpan.FromSeconds(21));
- cfg.InputRegistrationTimeout = TimeSpan.Zero;
+
+ arena.Rounds.Add(round.Id, round);
var ex = await Assert.ThrowsAsync<WabiSabiProtocolException>(async () => await handler.RegisterInputAsync(req));
Assert.Equal(Phase.InputRegistration, round.Phase);
Assert.Equal(WabiSabiProtocolErrorCode.WrongPhase, ex.ErrorCode);
@@ -330,7 +330,7 @@ namespace WalletWasabi.Tests.UnitTests.WabiSabi.Backend
WabiSabiConfig cfg = new();
var round = WabiSabiFactory.CreateRound(cfg);
round.Alices.Add(WabiSabiFactory.CreateAlice());
- Round blameRound = round.Blame();
+ Round blameRound = WabiSabiFactory.CreateBlameRound(round, cfg);
using Arena arena = await WabiSabiFactory.CreateAndStartArenaAsync(cfg, round, blameRound);
MockRpcClient rpc = new();
@@ -357,7 +357,7 @@ namespace WalletWasabi.Tests.UnitTests.WabiSabi.Backend
WabiSabiConfig cfg = new();
var round = WabiSabiFactory.CreateRound(cfg);
round.Alices.Add(WabiSabiFactory.CreateAlice(pair));
- Round blameRound = round.Blame();
+ Round blameRound = WabiSabiFactory.CreateBlameRound(round, cfg);
using Arena arena = await WabiSabiFactory.CreateAndStartArenaAsync(cfg, round, blameRound);
await using PostRequestHandler handler = new(cfg, new Prison(), arena, new MockRpcClient());
@@ -381,7 +381,7 @@ namespace WalletWasabi.Tests.UnitTests.WabiSabi.Backend
WabiSabiConfig cfg = new();
var round = WabiSabiFactory.CreateRound(cfg);
round.Alices.Add(WabiSabiFactory.CreateAlice(pair));
- Round blameRound = round.Blame();
+ Round blameRound = WabiSabiFactory.CreateBlameRound(round, cfg);
using Arena arena = await WabiSabiFactory.CreateAndStartArenaAsync(cfg, round, blameRound);
await using PostRequestHandler handler = new(cfg, prison, arena, new MockRpcClient());

MODIFY
WalletWasabi.Tests/UnitTests/WabiSabi/Backend/RoundCreationTests.cs
WalletWasabi.Tests/UnitTests/WabiSabi/Backend/RoundCreationTests.cs
@@ -6,6 +6,7 @@ using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
+using WalletWasabi.Tests.Helpers;
using WalletWasabi.WabiSabi.Backend;
using WalletWasabi.WabiSabi.Backend.Banning;
using WalletWasabi.WabiSabi.Backend.Rounds;
@@ -80,9 +81,9 @@ namespace WalletWasabi.Tests.UnitTests.WabiSabi.Backend
var round = Assert.Single(arena.Rounds).Value;
round.SetPhase(Phase.ConnectionConfirmation);
- var blame = round.Blame();
- Assert.Equal(Phase.InputRegistration, blame.Phase);
- arena.Rounds.Add(blame.Id, blame);
+ Round blameRound = WabiSabiFactory.CreateBlameRound(round, cfg);
+ Assert.Equal(Phase.InputRegistration, blameRound.Phase);
+ arena.Rounds.Add(blameRound.Id, blameRound);
await arena.TriggerAndWaitRoundAsync(TimeSpan.FromSeconds(21)).ConfigureAwait(false);
Assert.Equal(3, arena.Rounds.Count);
Assert.Equal(2, arena.Rounds.Where(x => x.Value.Phase == Phase.InputRegistration).Count());

MODIFY
WalletWasabi.Tests/UnitTests/WabiSabi/Backend/StepInputRegistrationTests.cs
WalletWasabi.Tests/UnitTests/WabiSabi/Backend/StepInputRegistrationTests.cs
@@ -61,16 +61,15 @@ namespace WalletWasabi.Tests.UnitTests.WabiSabi.Backend
{
WabiSabiConfig cfg = new()
{
- InputRegistrationTimeout = TimeSpan.FromHours(1),
+ InputRegistrationTimeout = TimeSpan.Zero,
MaxInputCountByRound = 4,
MinInputCountByRoundMultiplier = 0.5
};
var round = WabiSabiFactory.CreateRound(cfg);
- using Arena arena = await WabiSabiFactory.CreateAndStartArenaAsync(cfg, round);
-
round.Alices.Add(WabiSabiFactory.CreateAlice());
round.Alices.Add(WabiSabiFactory.CreateAlice());
- cfg.InputRegistrationTimeout = TimeSpan.Zero;
+
+ using Arena arena = await WabiSabiFactory.CreateAndStartArenaAsync(cfg, round);
await arena.TriggerAndWaitRoundAsync(TimeSpan.FromSeconds(21));
Assert.Equal(Phase.ConnectionConfirmation, round.Phase);

MODIFY
WalletWasabi/WabiSabi/Backend/PostRequests/InputRegistrationHandler.cs
WalletWasabi/WabiSabi/Backend/PostRequests/InputRegistrationHandler.cs
@@ -70,9 +70,7 @@ namespace WalletWasabi.WabiSabi.Backend.PostRequests
ZeroCredentialsRequest zeroWeightCredentialRequests,
IDictionary<Guid, Round> rounds,
Network network,
- uint maxInputCountByRound,
- TimeSpan inputRegistrationTimeout,
- TimeSpan connectionConfirmationTimeout)
+ uint maxInputCountByRound)
{
if (!rounds.TryGetValue(roundId, out var round))
{
@@ -122,7 +120,7 @@ namespace WalletWasabi.WabiSabi.Backend.PostRequests
throw new WabiSabiProtocolException(WabiSabiProtocolErrorCode.TooMuchWeight);
}
- if (round.IsInputRegistrationEnded(maxInputCountByRound, inputRegistrationTimeout))
+ if (round.IsInputRegistrationEnded(maxInputCountByRound))
{
throw new WabiSabiProtocolException(WabiSabiProtocolErrorCode.WrongPhase);
}
@@ -132,7 +130,7 @@ namespace WalletWasabi.WabiSabi.Backend.PostRequests
RemoveDuplicateAlices(rounds, alice);
- alice.SetDeadlineRelativeTo(connectionConfirmationTimeout);
+ alice.SetDeadlineRelativeTo(round.ConnectionConfirmationTimeout);
round.Alices.Add(alice);
return new(alice.Id, amountCredentialResponse, weightCredentialResponse);

MODIFY
WalletWasabi/WabiSabi/Backend/Rounds/Arena.cs
WalletWasabi/WabiSabi/Backend/Rounds/Arena.cs
@@ -65,7 +65,7 @@ namespace WalletWasabi.WabiSabi.Backend.Rounds
{
foreach (var round in Rounds.Values.Where(x =>
x.Phase == Phase.InputRegistration
- && x.IsInputRegistrationEnded(Config.MaxInputCountByRound, Config.InputRegistrationTimeout)))
+ && x.IsInputRegistrationEnded(Config.MaxInputCountByRound)))
{
if (round.InputCount < Config.MinInputCountByRound)
{
@@ -86,7 +86,7 @@ namespace WalletWasabi.WabiSabi.Backend.Rounds
{
round.SetPhase(Phase.OutputRegistration);
}
- else if (round.ConnectionConfirmationStart + Config.ConnectionConfirmationTimeout < DateTimeOffset.UtcNow)
+ else if (round.ConnectionConfirmationStart + round.ConnectionConfirmationTimeout < DateTimeOffset.UtcNow)
{
var alicesDidntConfirm = round.Alices.Where(x => !x.ConfirmedConnetion).ToArray();
foreach (var alice in alicesDidntConfirm)
@@ -114,7 +114,7 @@ namespace WalletWasabi.WabiSabi.Backend.Rounds
long aliceSum = round.Alices.Sum(x => x.CalculateRemainingAmountCredentials(round.FeeRate));
long bobSum = round.Bobs.Sum(x => x.CredentialAmount);
var diff = aliceSum - bobSum;
- if (diff == 0 || round.OutputRegistrationStart + Config.OutputRegistrationTimeout < DateTimeOffset.UtcNow)
+ if (diff == 0 || round.OutputRegistrationStart + round.OutputRegistrationTimeout < DateTimeOffset.UtcNow)
{
// Build a coinjoin:
var coinjoin = round.Coinjoin;
@@ -171,7 +171,7 @@ namespace WalletWasabi.WabiSabi.Backend.Rounds
await FailTransactionSigningPhaseAsync(round).ConfigureAwait(false);
}
}
- else if (round.TransactionSigningStart + Config.TransactionSigningTimeout < DateTimeOffset.UtcNow)
+ else if (round.TransactionSigningStart + round.TransactionSigningTimeout < DateTimeOffset.UtcNow)
{
await FailTransactionSigningPhaseAsync(round).ConfigureAwait(false);
}
@@ -204,8 +204,20 @@ namespace WalletWasabi.WabiSabi.Backend.Rounds
Prison.Note(alice, round.Id);
}
+ round.Alices.RemoveAll(x => alicesToBan.Contains(x));
Rounds.Remove(round.Id);
- var blameRound = round.Blame();
+
+ if (round.InputCount >= Config.MinInputCountByRound)
+ {
+ await CreateBlameRoundAsync(round).ConfigureAwait(false);
+ }
+ }
+
+ private async Task CreateBlameRoundAsync(Round round)
+ {
+ var feeRate = (await Rpc.EstimateSmartFeeAsync((int)Config.ConfirmationTarget, EstimateSmartFeeMode.Conservative).ConfigureAwait(false)).FeeRate;
+ RoundParameters parameters = new(Config, Network, Random, feeRate, blameOf: round);
+ Round blameRound = new(parameters);
Rounds.Add(blameRound.Id, blameRound);
}
@@ -223,7 +235,7 @@ namespace WalletWasabi.WabiSabi.Backend.Rounds
private void TimeoutAlices()
{
- foreach (var round in Rounds.Values.Where(x => !x.IsInputRegistrationEnded(Config.MaxInputCountByRound, Config.InputRegistrationTimeout)))
+ foreach (var round in Rounds.Values.Where(x => !x.IsInputRegistrationEnded(Config.MaxInputCountByRound)))
{
var removedAliceCount = round.Alices.RemoveAll(x => x.Deadline < DateTimeOffset.UtcNow);
if (removedAliceCount > 0)
@@ -273,9 +285,7 @@ namespace WalletWasabi.WabiSabi.Backend.Rounds
zeroWeightCredentialRequests,
Rounds,
Network,
- Config.MaxInputCountByRound,
- Config.InputRegistrationTimeout,
- Config.ConnectionConfirmationTimeout);
+ Config.MaxInputCountByRound);
}
}
@@ -327,7 +337,7 @@ namespace WalletWasabi.WabiSabi.Backend.Rounds
if (round.Phase == Phase.InputRegistration)
{
- alice.SetDeadlineRelativeTo(Config.ConnectionConfirmationTimeout);
+ alice.SetDeadlineRelativeTo(round.ConnectionConfirmationTimeout);
return new(
amountZeroCredentialResponse,
weightZeroCredentialResponse);
@@ -362,7 +372,7 @@ namespace WalletWasabi.WabiSabi.Backend.Rounds
var credentialAmount = -request.AmountCredentialRequests.Delta;
- var bob = new Bob(request.Script, credentialAmount);
+ Bob bob = new(request.Script, credentialAmount);
var outputValue = bob.CalculateOutputAmount(round.FeeRate);
if (outputValue < round.MinRegistrableAmount)

MODIFY
WalletWasabi/WabiSabi/Backend/Rounds/Round.cs
WalletWasabi/WabiSabi/Backend/Rounds/Round.cs
@@ -33,16 +33,6 @@ namespace WalletWasabi.WabiSabi.Backend.Rounds
Hash = new(HashHelpers.GenerateSha256Hash($"{Id}{MaxInputCountByAlice}{MinRegistrableAmount}{MaxRegistrableAmount}{RegistrableWeightCredentials}{AmountCredentialIssuerParameters}{WeightCredentialIssuerParameters}{FeeRate.SatoshiPerByte}"));
}
- private Round(Round blameOf) : this(blameOf.RoundParameters)
- {
- BlameOf = blameOf;
- BlameWhitelist = blameOf
- .Alices
- .SelectMany(x => x.Coins)
- .Select(x => x.Outpoint)
- .ToHashSet();
- }
-
public uint256 Hash { get; }
public Network Network => RoundParameters.Network;
public uint MaxInputCountByAlice => RoundParameters.MaxInputCountByAlice;
@@ -59,9 +49,16 @@ namespace WalletWasabi.WabiSabi.Backend.Rounds
public List<Alice> Alices { get; } = new();
public int InputCount => Alices.Sum(x => x.Coins.Count());
public List<Bob> Bobs { get; } = new();
- public Round? BlameOf { get; } = null;
- public bool IsBlameRound => BlameOf is not null;
- public ISet<OutPoint> BlameWhitelist { get; } = new HashSet<OutPoint>();
+
+ public Round? BlameOf => RoundParameters.BlameOf;
+ public bool IsBlameRound => RoundParameters.IsBlameRound;
+ public ISet<OutPoint> BlameWhitelist => RoundParameters.BlameWhitelist;
+
+ public TimeSpan InputRegistrationTimeout => RoundParameters.InputRegistrationTimeout;
+ public TimeSpan ConnectionConfirmationTimeout => RoundParameters.ConnectionConfirmationTimeout;
+ public TimeSpan OutputRegistrationTimeout => RoundParameters.OutputRegistrationTimeout;
+ public TimeSpan TransactionSigningTimeout => RoundParameters.TransactionSigningTimeout;
+
public byte[] UnsignedTxSecret { get; }
public Transaction Coinjoin { get; }
private RoundParameters RoundParameters { get; }
@@ -72,8 +69,6 @@ namespace WalletWasabi.WabiSabi.Backend.Rounds
public DateTimeOffset TransactionSigningStart { get; private set; }
public DateTimeOffset TransactionBroadcastingStart { get; private set; }
- public Round Blame() => new Round(this);
-
public void SetPhase(Phase phase)
{
Phase = phase;
@@ -96,7 +91,7 @@ namespace WalletWasabi.WabiSabi.Backend.Rounds
}
}
- public bool IsInputRegistrationEnded(uint maxInputCount, TimeSpan inputRegistrationTimeout)
+ public bool IsInputRegistrationEnded(uint maxInputCount)
{
if (Phase > Phase.InputRegistration)
{
@@ -108,7 +103,7 @@ namespace WalletWasabi.WabiSabi.Backend.Rounds
return true;
}
- if (InputRegistrationStart + inputRegistrationTimeout < DateTimeOffset.UtcNow)
+ if (InputRegistrationStart + InputRegistrationTimeout < DateTimeOffset.UtcNow)
{
return true;
}

MODIFY
WalletWasabi/WabiSabi/Backend/Rounds/RoundParameters.cs
WalletWasabi/WabiSabi/Backend/Rounds/RoundParameters.cs
@@ -14,15 +14,31 @@ namespace WalletWasabi.WabiSabi.Backend.Rounds
WabiSabiConfig wabiSabiConfig,
Network network,
WasabiRandom random,
- FeeRate feeRate)
+ FeeRate feeRate,
+ Round? blameOf = null)
{
Network = network;
Random = random;
FeeRate = feeRate;
+
MaxInputCountByAlice = wabiSabiConfig.MaxInputCountByAlice;
MinRegistrableAmount = wabiSabiConfig.MinRegistrableAmount;
MaxRegistrableAmount = wabiSabiConfig.MaxRegistrableAmount;
RegistrableWeightCredentials = wabiSabiConfig.RegistrableWeightCredentials;
+
+ BlameOf = blameOf;
+ IsBlameRound = BlameOf is not null;
+ BlameWhitelist = BlameOf
+ ?.Alices
+ .SelectMany(x => x.Coins)
+ .Select(x => x.Outpoint)
+ .ToHashSet()
+ ?? new HashSet<OutPoint>();
+
+ InputRegistrationTimeout = IsBlameRound ? wabiSabiConfig.BlameInputRegistrationTimeout : wabiSabiConfig.InputRegistrationTimeout;
+ ConnectionConfirmationTimeout = wabiSabiConfig.ConnectionConfirmationTimeout;
+ OutputRegistrationTimeout = wabiSabiConfig.OutputRegistrationTimeout;
+ TransactionSigningTimeout = wabiSabiConfig.TransactionSigningTimeout;
}
public WasabiRandom Random { get; }
@@ -32,5 +48,12 @@ namespace WalletWasabi.WabiSabi.Backend.Rounds
public Money MinRegistrableAmount { get; }
public Money MaxRegistrableAmount { get; }
public uint RegistrableWeightCredentials { get; }
+ public Round? BlameOf { get; }
+ public bool IsBlameRound { get; }
+ public ISet<OutPoint> BlameWhitelist { get; }
+ public TimeSpan InputRegistrationTimeout { get; }
+ public TimeSpan ConnectionConfirmationTimeout { get; }
+ public TimeSpan OutputRegistrationTimeout { get; }
+ public TimeSpan TransactionSigningTimeout { get; }
}
}

MODIFY
WalletWasabi/WabiSabi/Backend/WabiSabiConfig.cs
WalletWasabi/WabiSabi/Backend/WabiSabiConfig.cs
@@ -70,6 +70,11 @@ namespace WalletWasabi.WabiSabi.Backend
[JsonConverter(typeof(TimeSpanJsonConverter))]
public TimeSpan InputRegistrationTimeout { get; set; } = TimeSpan.FromHours(1);
+ [DefaultValueTimeSpan("0d 0h 3m 0s")]
+ [JsonProperty(PropertyName = "BlameInputRegistrationTimeout", DefaultValueHandling = DefaultValueHandling.Populate)]
+ [JsonConverter(typeof(TimeSpanJsonConverter))]
+ public TimeSpan BlameInputRegistrationTimeout { get; set; } = TimeSpan.FromMinutes(3);
+
[DefaultValueTimeSpan("0d 0h 1m 0s")]
[JsonProperty(PropertyName = "ConnectionConfirmationTimeout", DefaultValueHandling = DefaultValueHandling.Populate)]
[JsonConverter(typeof(TimeSpanJsonConverter))]

Implement blame round creation