Bookeeping total weight units per round
TODO comment regarding vsize allocations can be removed in or after
MODIFY
WalletWasabi.Tests/UnitTests/WabiSabi/Backend/PostRequests/RegisterInputFailureTests.cs
WalletWasabi.Tests/UnitTests/WabiSabi/Backend/PostRequests/RegisterInputFailureTests.cs
@@ -82,6 +82,33 @@ namespace WalletWasabi.Tests.UnitTests.WabiSabi.Backend.PostRequests
await arena.StopAsync(CancellationToken.None);
}
+ [Fact]
+ public async Task InputRegistrationFullWeightAsync()
+ {
+ WabiSabiConfig cfg = new();
+ var round = WabiSabiFactory.CreateRound(cfg);
+ using Arena arena = await WabiSabiFactory.CreateAndStartArenaAsync(cfg, round);
+ using Key key = new();
+
+ await using ArenaRequestHandler handler = new(cfg, new(), arena, WabiSabiFactory.CreateMockRpc(key));
+
+ // TODO add configuration parameter
+ round.InitialInputVsizeAllocation = (int)round.PerAliceVsizeAllocation;
+
+ // Add an alice
+ await arena.TriggerAndWaitRoundAsync(TimeSpan.FromSeconds(21));
+ round.Alices.Add(WabiSabiFactory.CreateAlice());
+
+ Assert.Equal(0, round.RemainingInputVsizeAllocation);
+
+ // try to add an additional input
+ var req = WabiSabiFactory.CreateInputsRegistrationRequest(key, round);
+ var ex = await Assert.ThrowsAsync<WabiSabiProtocolException>(async () => await handler.RegisterInputAsync(req));
+ Assert.Equal(WabiSabiProtocolErrorCode.TooMuchTotalWeight, ex.ErrorCode);
+
+ await arena.StopAsync(CancellationToken.None);
+ }
+
[Fact]
public async Task InputRegistrationTimedoutAsync()
{

MODIFY
WalletWasabi.Tests/UnitTests/WabiSabi/Backend/PostRequests/RegisterInputSuccessTests.cs
WalletWasabi.Tests/UnitTests/WabiSabi/Backend/PostRequests/RegisterInputSuccessTests.cs
@@ -81,6 +81,8 @@ namespace WalletWasabi.Tests.UnitTests.WabiSabi.Backend.PostRequests
// Make sure an Alice have already been registered with the same input.
var preAlice = WabiSabiFactory.CreateAlice(req.InputRoundSignaturePairs);
+
+ var initialRemaining = anotherRound.RemainingInputVsizeAllocation;
anotherRound.Alices.Add(preAlice);
await using ArenaRequestHandler handler = new(cfg, new(), arena, WabiSabiFactory.CreateMockRpc(key));
@@ -88,6 +90,7 @@ namespace WalletWasabi.Tests.UnitTests.WabiSabi.Backend.PostRequests
var resp = await handler.RegisterInputAsync(req);
AssertSingleAliceSuccessfullyRegistered(round, minAliceDeadline, resp);
Assert.Empty(anotherRound.Alices);
+ Assert.Equal(initialRemaining, anotherRound.RemainingInputVsizeAllocation);
await arena.StopAsync(CancellationToken.None);
}

MODIFY
WalletWasabi.Tests/UnitTests/WabiSabi/Backend/PostRequests/RemoveInputTests.cs
WalletWasabi.Tests/UnitTests/WabiSabi/Backend/PostRequests/RemoveInputTests.cs
@@ -22,8 +22,11 @@ namespace WalletWasabi.Tests.UnitTests.WabiSabi.Backend.PostRequests
{
WabiSabiConfig cfg = new();
var round = WabiSabiFactory.CreateRound(cfg);
+ var initialRemaining = round.RemainingInputVsizeAllocation;
var alice = WabiSabiFactory.CreateAlice();
round.Alices.Add(alice);
+ Assert.True(round.RemainingInputVsizeAllocation < initialRemaining);
+
using Arena arena = await WabiSabiFactory.CreateAndStartArenaAsync(cfg, round);
await using ArenaRequestHandler handler = new(cfg, new Prison(), arena, new MockRpcClient());
@@ -36,6 +39,10 @@ namespace WalletWasabi.Tests.UnitTests.WabiSabi.Backend.PostRequests
req = new InputsRemovalRequest(round.Id, alice.Id);
await handler.RemoveInputAsync(req);
+ // Ensure that removing an alice freed up the input weight
+ // allocation from the round
+ Assert.Equal(initialRemaining, round.RemainingInputVsizeAllocation);
+
await arena.StopAsync(CancellationToken.None);
}

MODIFY
WalletWasabi/WabiSabi/Backend/Models/WabiSabiProtocolErrorCode.cs
WalletWasabi/WabiSabi/Backend/Models/WabiSabiProtocolErrorCode.cs
@@ -33,6 +33,7 @@ namespace WalletWasabi.WabiSabi.Backend.Models
InsufficientFees,
SizeLimitExceeded,
DustOutput,
- UneconomicalInput
+ UneconomicalInput,
+ TooMuchTotalWeight
}
}

MODIFY
WalletWasabi/WabiSabi/Backend/PostRequests/InputRegistrationHandler.cs
WalletWasabi/WabiSabi/Backend/PostRequests/InputRegistrationHandler.cs
@@ -79,9 +79,8 @@ namespace WalletWasabi.WabiSabi.Backend.PostRequests
throw new WabiSabiProtocolException(WabiSabiProtocolErrorCode.RoundNotFound);
}
- var alice = new Alice(coinRoundSignaturePairs);
+ var coins = coinRoundSignaturePairs.Select(x => x.Key);
- var coins = alice.Coins;
if (round.MaxInputCountByAlice < coins.Count())
{
throw new WabiSabiProtocolException(WabiSabiProtocolErrorCode.TooManyInputs);
@@ -91,9 +90,7 @@ namespace WalletWasabi.WabiSabi.Backend.PostRequests
throw new WabiSabiProtocolException(WabiSabiProtocolErrorCode.InputNotWhitelisted);
}
- var inputValueSum = Money.Zero;
- var inputWeightSum = 0;
- foreach (var coinRoundSignaturePair in alice.CoinRoundSignaturePairs)
+ foreach (var coinRoundSignaturePair in coinRoundSignaturePairs)
{
var coin = coinRoundSignaturePair.Key;
var signature = coinRoundSignaturePair.Value;
@@ -103,22 +100,20 @@ namespace WalletWasabi.WabiSabi.Backend.PostRequests
{
throw new WabiSabiProtocolException(WabiSabiProtocolErrorCode.WrongRoundSignature);
}
- inputValueSum += coin.TxOut.Value;
-
- // Convert conservative P2WPKH size in virtual bytes to weight units.
- inputWeightSum += Constants.WitnessScaleFactor * coin.TxOut.ScriptPubKey.EstimateInputVsize();
}
- if (inputValueSum < round.MinRegistrableAmount)
+ var alice = new Alice(coinRoundSignaturePairs);
+
+ if (alice.TotalInputAmount < round.MinRegistrableAmount)
{
throw new WabiSabiProtocolException(WabiSabiProtocolErrorCode.NotEnoughFunds);
}
- if (inputValueSum > round.MaxRegistrableAmount)
+ if (alice.TotalInputAmount > round.MaxRegistrableAmount)
{
throw new WabiSabiProtocolException(WabiSabiProtocolErrorCode.TooMuchFunds);
}
- if (inputWeightSum > round.RegistrableWeightCredentials)
+ if (alice.TotalInputVsize > round.RegistrableWeightCredentials)
{
throw new WabiSabiProtocolException(WabiSabiProtocolErrorCode.TooMuchWeight);
}
@@ -128,6 +123,10 @@ namespace WalletWasabi.WabiSabi.Backend.PostRequests
throw new WabiSabiProtocolException(WabiSabiProtocolErrorCode.WrongPhase);
}
+ if (round.RemainingInputVsizeAllocation < (round.RegistrableWeightCredentials+3)/4) // TODO standardize on vsize
+ {
+ throw new WabiSabiProtocolException(WabiSabiProtocolErrorCode.TooMuchTotalWeight);
+ }
var commitAmountCredentialResponse = round.AmountCredentialIssuer.PrepareResponse(zeroAmountCredentialRequests);
var commitWeightCredentialResponse = round.WeightCredentialIssuer.PrepareResponse(zeroWeightCredentialRequests);

MODIFY
WalletWasabi/WabiSabi/Backend/Rounds/Round.cs
WalletWasabi/WabiSabi/Backend/Rounds/Round.cs
@@ -65,6 +65,8 @@ namespace WalletWasabi.WabiSabi.Backend.Rounds
public DateTimeOffset OutputRegistrationStart { get; private set; }
public DateTimeOffset TransactionSigningStart { get; private set; }
public DateTimeOffset TransactionBroadcastingStart { get; private set; }
+ public int InitialInputVsizeAllocation { get; set; } = 99954; // TODO compute as CoinjoinState.Parameters.MaxWeight - CoinjoinState.Parameters.SharedOverhead, mutable for testing until then
+ public int RemainingInputVsizeAllocation => InitialInputVsizeAllocation - Alices.Count() * (int)((RegistrableWeightCredentials+3)/4); // TODO remove weight
public void SetPhase(Phase phase)
{

Bookeeping total weight units per round

TODO comment regarding vsize allocations can be removed in or after

Co-Authored-By: Yuval Kogman <nothingmuch@woobling.org>