Make the `RoundParameters` be the Event zero.
MODIFY
WalletWasabi.Tests/UnitTests/WabiSabi/Backend/Rounds/MultipartyTransactionStateTests.cs
WalletWasabi.Tests/UnitTests/WabiSabi/Backend/Rounds/MultipartyTransactionStateTests.cs
@@ -35,18 +35,18 @@ public class MultipartyTransactionStateTests
Assert.Equal(state3.Outputs, diffd30.Outputs);
// Only one event is missing
- var diffd32 = state3.GetStateFrom(2);
+ var diffd32 = state3.GetStateFrom(3);
var input = Assert.Single(diffd32.Inputs);
Assert.Equal(coin3.Outpoint, input.Outpoint);
// Two events are missing
- var diffd31 = state3.GetStateFrom(1);
+ var diffd31 = state3.GetStateFrom(2);
Assert.Collection(diffd31.Inputs,
x => Assert.Equal(coin2.Outpoint, x.Outpoint),
x => Assert.Equal(coin3.Outpoint, x.Outpoint));
// No event is missing (already updated)
- var diffd33 = state3.GetStateFrom(3);
+ var diffd33 = state3.GetStateFrom(4);
Assert.Empty(diffd33.Inputs);
Assert.Empty(diffd33.Outputs);
@@ -65,7 +65,7 @@ public class MultipartyTransactionStateTests
var diff21 = state2.GetStateFrom(1);
var diff32 = state3.GetStateFrom(2);
var clientState1 = state1;
- var clientState3 = state3.GetStateFrom(1).AddPreviousStates(clientState1);
+ var clientState3 = state3.GetStateFrom(2).AddPreviousStates(clientState1);
Assert.Equal(state3.Inputs, clientState3.Inputs);
Assert.Equal(state3.Outputs, clientState3.Outputs);
Assert.Equal(clientState3.Inputs, state3.Inputs);

MODIFY
WalletWasabi.Tests/UnitTests/WabiSabi/Client/ArenaClientTests.cs
WalletWasabi.Tests/UnitTests/WabiSabi/Client/ArenaClientTests.cs
@@ -91,7 +91,7 @@ public class ArenaClientTests
using var destinationKey2 = new Key();
var p2wpkhScriptSize = (long)destinationKey1.PubKey.WitHash.ScriptPubKey.EstimateOutputVsize();
- var vsizesToRequest = new[] { roundState.MaxVsizeAllocationPerAlice - (inputVsize + 2 * p2wpkhScriptSize), 2 * p2wpkhScriptSize };
+ var vsizesToRequest = new[] { round.Parameters.MaxVsizeAllocationPerAlice - (inputVsize + 2 * p2wpkhScriptSize), 2 * p2wpkhScriptSize };
// Phase: Input Registration
Assert.Equal(Phase.InputRegistration, round.Phase);

MODIFY
WalletWasabi/WabiSabi/Client/AliceClient.cs
WalletWasabi/WabiSabi/Client/AliceClient.cs
@@ -25,17 +25,18 @@ public class AliceClient
IEnumerable<Credential> issuedVsizeCredentials,
bool isPayingZeroCoordinationFee)
{
+ var roundParameters = roundState.CoinjoinState.Parameters;
AliceId = aliceId;
RoundId = roundState.Id;
ArenaClient = arenaClient;
SmartCoin = coin;
OwnershipProof = ownershipProof;
- FeeRate = roundState.FeeRate;
- CoordinationFeeRate = roundState.CoordinationFeeRate;
+ FeeRate = roundParameters.MiningFeeRate;
+ CoordinationFeeRate = roundParameters.CoordinationFeeRate;
IssuedAmountCredentials = issuedAmountCredentials;
IssuedVsizeCredentials = issuedVsizeCredentials;
- MaxVsizeAllocationPerAlice = roundState.MaxVsizeAllocationPerAlice;
- ConfirmationTimeout = roundState.ConnectionConfirmationTimeout / 2;
+ MaxVsizeAllocationPerAlice = roundParameters.MaxVsizeAllocationPerAlice;
+ ConfirmationTimeout = roundParameters.ConnectionConfirmationTimeout / 2;
IsPayingZeroCoordinationFee = isPayingZeroCoordinationFee;
}

MODIFY
WalletWasabi/WabiSabi/Client/CoinJoinClient.cs
WalletWasabi/WabiSabi/Client/CoinJoinClient.cs
@@ -80,7 +80,7 @@ public class CoinJoinClient
&& roundState.CoinjoinState.Parameters.AllowedOutputAmounts.Min < MinimumOutputAmountSanity
&& roundState.Phase == Phase.InputRegistration
&& roundState.BlameOf == uint256.Zero
- && IsRoundEconomic(roundState.FeeRate)
+ && IsRoundEconomic(roundState.CoinjoinState.Parameters.MiningFeeRate)
&& roundState.Id != excludeRound,
token)
.ConfigureAwait(false);
@@ -107,7 +107,7 @@ public class CoinJoinClient
throw new InvalidOperationException($"Blame Round ({roundState.Id}): Abandoning: the minimum output amount is too high.");
}
- if (!IsRoundEconomic(roundState.FeeRate))
+ if (!IsRoundEconomic(roundState.CoinjoinState.Parameters.MiningFeeRate))
{
throw new InvalidOperationException($"Blame Round ({roundState.Id}): Abandoning: the round is not economic.");
}
@@ -126,12 +126,13 @@ public class CoinJoinClient
do
{
currentRoundState = await WaitForRoundAsync(excludeRound, cancellationToken).ConfigureAwait(false);
- coins = SelectCoinsForRound(coinCandidates, currentRoundState.CoinjoinState.Parameters, ConsolidationMode, MinAnonScoreTarget, SecureRandom);
+ RoundParameters roundParameteers = currentRoundState.CoinjoinState.Parameters;
+ coins = SelectCoinsForRound(coinCandidates, roundParameteers, ConsolidationMode, MinAnonScoreTarget, SecureRandom);
- if (currentRoundState.MaxSuggestedAmount != default && coins.Any(c => c.Amount > currentRoundState.MaxSuggestedAmount))
+ if (roundParameteers.MaxSuggestedAmount != default && coins.Any(c => c.Amount > roundParameteers.MaxSuggestedAmount))
{
excludeRound = currentRoundState.Id;
- currentRoundState.LogInfo($"Skipping the round for more optimal mixing. Max suggested amount is '{currentRoundState.MaxSuggestedAmount}' BTC, biggest coin amount is: '{coins.Select(c => c.Amount).Max()}' BTC.");
+ currentRoundState.LogInfo($"Skipping the round for more optimal mixing. Max suggested amount is '{roundParameteers.MaxSuggestedAmount}' BTC, biggest coin amount is: '{coins.Select(c => c.Amount).Max()}' BTC.");
continue;
}
@@ -367,7 +368,8 @@ public class CoinJoinClient
private void LogCoinJoinSummary(ImmutableArray<AliceClient> registeredAliceClients, IEnumerable<TxOut> myOutputs, Transaction unsignedCoinJoinTransaction, RoundState roundState)
{
- var feeRate = roundState.FeeRate;
+ RoundParameters roundParameters = roundState.CoinjoinState.Parameters;
+ FeeRate feeRate = roundParameters.MiningFeeRate;
var totalEffectiveInputAmount = Money.Satoshis(registeredAliceClients.Sum(a => a.EffectiveValue));
var totalEffectiveOutputAmount = Money.Satoshis(myOutputs.Sum(a => a.Value - feeRate.GetFee(a.ScriptPubKey.EstimateOutputVsize())));
@@ -380,7 +382,7 @@ public class CoinJoinClient
var inputNetworkFee = Money.Satoshis(registeredAliceClients.Sum(alice => feeRate.GetFee(alice.SmartCoin.Coin.ScriptPubKey.EstimateInputVsize())));
var outputNetworkFee = Money.Satoshis(myOutputs.Sum(output => feeRate.GetFee(output.ScriptPubKey.EstimateOutputVsize())));
var totalNetworkFee = inputNetworkFee + outputNetworkFee;
- var totalCoordinationFee = Money.Satoshis(registeredAliceClients.Where(a => a.IsPayingZeroCoordinationFee).Sum(a => roundState.CoordinationFeeRate.GetFee(a.SmartCoin.Amount)));
+ var totalCoordinationFee = Money.Satoshis(registeredAliceClients.Where(a => a.IsPayingZeroCoordinationFee).Sum(a => roundParameters.CoordinationFeeRate.GetFee(a.SmartCoin.Amount)));
string[] summary = new string[]
{
@@ -527,7 +529,8 @@ public class CoinJoinClient
{
// Waiting for OutputRegistration phase, all the Alices confirmed their connections, so the list of the inputs will be complete.
var roundState = await RoundStatusUpdater.CreateRoundAwaiter(roundId, Phase.OutputRegistration, cancellationToken).ConfigureAwait(false);
- var remainingTime = roundState.OutputRegistrationTimeout - RoundStatusUpdater.Period;
+ var roundParameters = roundState.CoinjoinState.Parameters;
+ var remainingTime = roundParameters.OutputRegistrationTimeout - RoundStatusUpdater.Period;
var outputRegistrationEndTime = DateTimeOffset.UtcNow + (remainingTime * 0.8); // 80% of the time.
var readyToSignEndTime = outputRegistrationEndTime + remainingTime * 0.2; // 20% of the time.
@@ -541,17 +544,17 @@ public class CoinJoinClient
// Calculate outputs values
var constructionState = roundState.Assert<ConstructionState>();
- AmountDecomposer amountDecomposer = new(roundState.FeeRate, roundState.CoinjoinState.Parameters.AllowedOutputAmounts, Constants.P2wpkhOutputVirtualSize, Constants.P2wpkhInputVirtualSize, (int)availableVsize);
+ AmountDecomposer amountDecomposer = new(roundParameters.MiningFeeRate, roundParameters.AllowedOutputAmounts, Constants.P2wpkhOutputVirtualSize, Constants.P2wpkhInputVirtualSize, (int)availableVsize);
var theirCoins = constructionState.Inputs.Where(x => !registeredCoins.Any(y => x.Outpoint == y.Outpoint));
var registeredCoinEffectiveValues = registeredAliceClients.Select(x => x.EffectiveValue);
- var theirCoinEffectiveValues = theirCoins.Select(x => x.EffectiveValue(roundState.FeeRate, roundState.CoordinationFeeRate));
+ var theirCoinEffectiveValues = theirCoins.Select(x => x.EffectiveValue(roundParameters.MiningFeeRate, roundParameters.CoordinationFeeRate));
var outputValues = amountDecomposer.Decompose(registeredCoinEffectiveValues, theirCoinEffectiveValues);
// Get as many destinations as outputs we need.
var destinations = DestinationProvider.GetNextDestinations(outputValues.Count()).ToArray();
var outputTxOuts = outputValues.Zip(destinations, (amount, destination) => new TxOut(amount, destination.ScriptPubKey));
- DependencyGraph dependencyGraph = DependencyGraph.ResolveCredentialDependencies(inputEffectiveValuesAndSizes, outputTxOuts, roundState.FeeRate, roundState.CoordinationFeeRate, roundState.MaxVsizeAllocationPerAlice);
+ DependencyGraph dependencyGraph = DependencyGraph.ResolveCredentialDependencies(inputEffectiveValuesAndSizes, outputTxOuts, roundParameters.MiningFeeRate, roundParameters.CoordinationFeeRate, roundParameters.MaxVsizeAllocationPerAlice);
DependencyGraphTaskScheduler scheduler = new(dependencyGraph);
// Re-issuances.
@@ -578,7 +581,7 @@ public class CoinJoinClient
{
// Signing.
var roundState = await RoundStatusUpdater.CreateRoundAwaiter(roundId, Phase.TransactionSigning, cancellationToken).ConfigureAwait(false);
- var remainingTime = roundState.TransactionSigningTimeout - RoundStatusUpdater.Period;
+ var remainingTime = roundState.CoinjoinState.Parameters.TransactionSigningTimeout - RoundStatusUpdater.Period;
var signingStateEndTime = DateTimeOffset.UtcNow + remainingTime;
using CancellationTokenSource phaseTimeoutCts = new(remainingTime + ExtraPhaseTimeoutMargin);

MODIFY
WalletWasabi/WabiSabi/Models/MultipartyTransaction/MultipartyTransactionState.cs
WalletWasabi/WabiSabi/Models/MultipartyTransaction/MultipartyTransactionState.cs
@@ -9,6 +9,8 @@ using WalletWasabi.WabiSabi.Backend.Rounds;
namespace WalletWasabi.WabiSabi.Models.MultipartyTransaction;
public interface IEvent{};
+
+public record RoundCreated(RoundParameters RoundParameters) : IEvent;
public record InputAdded (Coin Coin) : IEvent;
public record OutputAdded (TxOut Output) : IEvent;
@@ -16,10 +18,13 @@ public abstract record MultipartyTransactionState
{
protected MultipartyTransactionState(RoundParameters parameters)
{
- Parameters = parameters;
+ var builder = ImmutableList.CreateBuilder<IEvent>();
+ builder.Add(new RoundCreated(parameters));
+ Events = builder.ToImmutable();
}
- public RoundParameters Parameters { get; }
+ [JsonIgnore]
+ public RoundParameters Parameters => Events.OfType<RoundCreated>().Single().RoundParameters;
[JsonIgnore]
public IEnumerable<Coin> Inputs => Events.OfType<InputAdded>().Select(x => x.Coin);
@@ -35,8 +40,6 @@ public abstract record MultipartyTransactionState
[JsonIgnore]
public int EstimatedVsize => MultipartyTransactionParameters.SharedOverhead + EstimatedInputsVsize + OutputsVsize;
- [JsonIgnore]
- public int MaxTransactionSize => Parameters.MaxTransactionSize;
// With no coordinator fees we can't ensure that the shared overhead
// of the transaction also pays at the nominal feerate so this will have

MODIFY
WalletWasabi/WabiSabi/Models/RoundState.cs
WalletWasabi/WabiSabi/Models/RoundState.cs
@@ -7,70 +7,41 @@ using WalletWasabi.WabiSabi.Models.MultipartyTransaction;
namespace WalletWasabi.WabiSabi.Models;
-public record RoundState(
+public record RoundState(uint256 Id,
uint256 BlameOf,
CredentialIssuerParameters AmountCredentialIssuerParameters,
CredentialIssuerParameters VsizeCredentialIssuerParameters,
- FeeRate FeeRate,
- CoordinationFeeRate CoordinationFeeRate,
Phase Phase,
bool WasTransactionBroadcast,
DateTimeOffset InputRegistrationStart,
TimeSpan InputRegistrationTimeout,
- TimeSpan ConnectionConfirmationTimeout,
- TimeSpan OutputRegistrationTimeout,
- TimeSpan TransactionSigningTimeout,
- long MaxAmountCredentialValue,
- long MaxVsizeCredentialValue,
- long MaxVsizeAllocationPerAlice,
- long MaxSuggestedAmount,
MultipartyTransactionState CoinjoinState)
{
- private uint256? _id;
-
- public uint256 Id => _id ??= CalculateHash();
-
public DateTimeOffset InputRegistrationEnd => InputRegistrationStart + InputRegistrationTimeout;
public static RoundState FromRound(Round round, int stateId = 0) =>
new(
+ round.Id,
round is BlameRound blameRound ? blameRound.BlameOf.Id : uint256.Zero,
round.AmountCredentialIssuerParameters,
round.VsizeCredentialIssuerParameters,
- round.Parameters.MiningFeeRate,
- round.Parameters.CoordinationFeeRate,
round.Phase,
round.WasTransactionBroadcast,
round.InputRegistrationTimeFrame.StartTime,
round.InputRegistrationTimeFrame.Duration,
- round.ConnectionConfirmationTimeFrame.Duration,
- round.OutputRegistrationTimeFrame.Duration,
- round.TransactionSigningTimeFrame.Duration,
- round.Parameters.MaxAmountCredentialValue,
- round.Parameters.MaxVsizeCredentialValue,
- round.Parameters.MaxVsizeAllocationPerAlice,
- round.Parameters.MaxSuggestedAmount,
round.CoinjoinState.GetStateFrom(stateId)
);
public RoundState GetSubState(int skipFromBaseState) =>
new(
+ Id,
BlameOf,
AmountCredentialIssuerParameters,
VsizeCredentialIssuerParameters,
- FeeRate,
- CoordinationFeeRate,
Phase,
WasTransactionBroadcast,
InputRegistrationStart,
InputRegistrationTimeout,
- ConnectionConfirmationTimeout,
- OutputRegistrationTimeout,
- TransactionSigningTimeout,
- MaxAmountCredentialValue,
- MaxVsizeCredentialValue,
- MaxVsizeAllocationPerAlice,
- MaxSuggestedAmount,
CoinjoinState.GetStateFrom(skipFromBaseState)
);
@@ -82,31 +53,8 @@ public record RoundState(
};
public WabiSabiClient CreateAmountCredentialClient(WasabiRandom random) =>
- new(AmountCredentialIssuerParameters, random, MaxAmountCredentialValue);
+ new(AmountCredentialIssuerParameters, random, CoinjoinState.Parameters.MaxAmountCredentialValue);
public WabiSabiClient CreateVsizeCredentialClient(WasabiRandom random) =>
- new(VsizeCredentialIssuerParameters, random, MaxVsizeCredentialValue);
-
- private uint256 CalculateHash() =>
- RoundHasher.CalculateHash(
- InputRegistrationStart,
- InputRegistrationTimeout,
- ConnectionConfirmationTimeout,
- OutputRegistrationTimeout,
- TransactionSigningTimeout,
- CoinjoinState.Parameters.AllowedInputAmounts,
- CoinjoinState.Parameters.AllowedInputTypes,
- CoinjoinState.Parameters.AllowedOutputAmounts,
- CoinjoinState.Parameters.AllowedOutputTypes,
- CoinjoinState.Parameters.Network,
- CoinjoinState.Parameters.MiningFeeRate.FeePerK,
- CoinjoinState.Parameters.CoordinationFeeRate,
- CoinjoinState.Parameters.MaxTransactionSize,
- CoinjoinState.Parameters.MinRelayTxFee.FeePerK,
- MaxAmountCredentialValue,
- MaxVsizeCredentialValue,
- MaxVsizeAllocationPerAlice,
- MaxSuggestedAmount,
- AmountCredentialIssuerParameters,
- VsizeCredentialIssuerParameters);
+ new(VsizeCredentialIssuerParameters, random, CoinjoinState.Parameters.MaxVsizeCredentialValue);
}

MODIFY
WalletWasabi/WabiSabi/Models/Serialization/CoinJoinEventJsonConverter.cs
WalletWasabi/WabiSabi/Models/Serialization/CoinJoinEventJsonConverter.cs
@@ -4,7 +4,7 @@ namespace WalletWasabi.WabiSabi.Models.Serialization;
public class CoinJoinEventJsonConverter : GenericInterfaceJsonConverter<IEvent>
{
- public CoinJoinEventJsonConverter() : base(new[] { typeof(InputAdded), typeof(OutputAdded) })
+ public CoinJoinEventJsonConverter() : base(new[] { typeof(InputAdded), typeof(OutputAdded), typeof(RoundCreated) })
{
}
}

Make the `RoundParameters` be the Event zero.