Remove `MultipartyTransactionStateExtensions`
MODIFY
WalletWasabi.Tests/UnitTests/WabiSabi/Backend/PhaseStepping/StepOutputRegistrationTests.cs
WalletWasabi.Tests/UnitTests/WabiSabi/Backend/PhaseStepping/StepOutputRegistrationTests.cs
@@ -77,7 +77,7 @@ namespace WalletWasabi.Tests.UnitTests.WabiSabi.Backend.PhaseStepping
}
await arena.TriggerAndWaitRoundAsync(TimeSpan.FromSeconds(21));
Assert.Equal(Phase.TransactionSigning, round.Phase);
- var tx = round.CoinjoinState.AssertSigning().CreateTransaction();
+ var tx = round.Assert<SigningState>().CreateTransaction();
Assert.Equal(2, tx.Inputs.Count);
Assert.Equal(2, tx.Outputs.Count);
@@ -141,7 +141,7 @@ irreq1.OwnershipProof,
var orresp = await arena.RegisterOutputAsync(WabiSabiFactory.CreateOutputRegistrationRequests(round, ccresps).First());
await arena.TriggerAndWaitRoundAsync(TimeSpan.FromSeconds(21));
Assert.Equal(Phase.TransactionSigning, round.Phase);
- var tx = round.CoinjoinState.AssertSigning().CreateTransaction();
+ var tx = round.Assert<SigningState>().CreateTransaction();
Assert.Equal(2, tx.Inputs.Count);
Assert.Equal(2, tx.Outputs.Count);
Assert.Contains(cfg.BlameScript, tx.Outputs.Select(x => x.ScriptPubKey));
@@ -212,14 +212,14 @@ irreq1.OwnershipProof,
// Add another input. The input must be able to pay for itself, but
// the remaining amount after deducting the fees needs to be less
// than the minimum.
- var txParams = round.CoinjoinState.AssertConstruction().Parameters;
+ var txParams = round.Assert<ConstructionState>().Parameters;
var extraAlice = WabiSabiFactory.CreateAlice(value: txParams.FeeRate.GetFee(Constants.P2wpkhInputVirtualSize) + txParams.AllowedOutputAmounts.Min - new Money(1L));
round.Alices.Add(extraAlice);
- round.CoinjoinState = round.CoinjoinState.AssertConstruction().AddInput(extraAlice.Coin);
+ round.CoinjoinState = round.Assert<ConstructionState>().AddInput(extraAlice.Coin);
await arena.TriggerAndWaitRoundAsync(TimeSpan.FromSeconds(21));
Assert.Equal(Phase.TransactionSigning, round.Phase);
- var tx = round.CoinjoinState.AssertSigning().CreateTransaction();
+ var tx = round.Assert<SigningState>().CreateTransaction();
Assert.Equal(3, tx.Inputs.Count);
Assert.Equal(2, tx.Outputs.Count);
Assert.DoesNotContain(cfg.BlameScript, tx.Outputs.Select(x => x.ScriptPubKey));

MODIFY
WalletWasabi.Tests/UnitTests/WabiSabi/Backend/PhaseStepping/StepTransactionSigningTests.cs
WalletWasabi.Tests/UnitTests/WabiSabi/Backend/PhaseStepping/StepTransactionSigningTests.cs
@@ -78,7 +78,7 @@ irreq2.OwnershipProof,
await arena.TriggerAndWaitRoundAsync(TimeSpan.FromSeconds(21));
Assert.Equal(Phase.TransactionSigning, round.Phase);
- var signedCoinJoin = round.CoinjoinState.AssertSigning().CreateTransaction();
+ var signedCoinJoin = round.Assert<SigningState>().CreateTransaction();
var coin1 = alice1.Coin;
var coin2 = alice2.Coin;
var idx1 = signedCoinJoin.Inputs.IndexOf(signedCoinJoin.Inputs.Single(x => x.PrevOut == coin1.Outpoint));
@@ -161,7 +161,7 @@ irreq2.OwnershipProof,
await arena.TriggerAndWaitRoundAsync(TimeSpan.FromSeconds(21));
Assert.Equal(Phase.TransactionSigning, round.Phase);
- var signedCoinJoin = round.CoinjoinState.AssertSigning().CreateTransaction();
+ var signedCoinJoin = round.Assert<SigningState>().CreateTransaction();
var coin1 = alice1.Coin;
var coin2 = alice2.Coin;
var idx1 = signedCoinJoin.Inputs.IndexOf(signedCoinJoin.Inputs.Single(x => x.PrevOut == coin1.Outpoint));
@@ -245,7 +245,7 @@ irreq2.OwnershipProof,
await arena.TriggerAndWaitRoundAsync(TimeSpan.FromSeconds(21));
Assert.Equal(Phase.TransactionSigning, round.Phase);
- var signedCoinJoin = round.CoinjoinState.AssertSigning().CreateTransaction();
+ var signedCoinJoin = round.Assert<SigningState>().CreateTransaction();
var coin1 = alice1.Coin;
var coin2 = alice2.Coin;
var idx1 = signedCoinJoin.Inputs.IndexOf(signedCoinJoin.Inputs.Single(x => x.PrevOut == coin1.Outpoint));
@@ -333,7 +333,7 @@ irreq2.OwnershipProof,
await arena.TriggerAndWaitRoundAsync(TimeSpan.FromSeconds(21));
Assert.Equal(Phase.TransactionSigning, round.Phase);
- var signedCoinJoin = round.CoinjoinState.AssertSigning().CreateTransaction();
+ var signedCoinJoin = round.Assert<SigningState>().CreateTransaction();
var coin1 = alice1.Coin;
var idx1 = signedCoinJoin.Inputs.IndexOf(signedCoinJoin.Inputs.Single(x => x.PrevOut == coin1.Outpoint));
signedCoinJoin.Sign(key1.GetBitcoinSecret(Network.Main), coin1);
@@ -415,11 +415,11 @@ irreq2.OwnershipProof,
var alice3 = WabiSabiFactory.CreateAlice();
alice3.ConfirmedConnection = true;
round.Alices.Add(alice3);
- round.CoinjoinState = round.CoinjoinState.AssertConstruction().AddInput(alice3.Coin);
+ round.CoinjoinState = round.Assert<ConstructionState>().AddInput(alice3.Coin);
await arena.TriggerAndWaitRoundAsync(TimeSpan.FromSeconds(21));
Assert.Equal(Phase.TransactionSigning, round.Phase);
- var signedCoinJoin = round.CoinjoinState.AssertSigning().CreateTransaction();
+ var signedCoinJoin = round.Assert<SigningState>().CreateTransaction();
var coin1 = alice1.Coin;
var coin2 = alice2.Coin;
var idx1 = signedCoinJoin.Inputs.IndexOf(signedCoinJoin.Inputs.Single(x => x.PrevOut == coin1.Outpoint));

MODIFY
WalletWasabi.Tests/UnitTests/WabiSabi/Backend/PostRequests/SignTransactionTests.cs
WalletWasabi.Tests/UnitTests/WabiSabi/Backend/PostRequests/SignTransactionTests.cs
@@ -29,13 +29,13 @@ namespace WalletWasabi.Tests.UnitTests.WabiSabi.Backend.PostRequests
round.SetPhase(Phase.TransactionSigning);
using Arena arena = await WabiSabiFactory.CreateAndStartArenaAsync(cfg, round);
- var aliceSignedCoinJoin = round.CoinjoinState.AssertSigning().CreateUnsignedTransaction();
+ var aliceSignedCoinJoin = round.Assert<SigningState>().CreateUnsignedTransaction();
aliceSignedCoinJoin.Sign(key.GetBitcoinSecret(Network.Main), alice.Coin);
var req = new TransactionSignaturesRequest(round.Id, new[] { new InputWitnessPair(0, aliceSignedCoinJoin.Inputs[0].WitScript) });
await using ArenaRequestHandler handler = new(cfg, new Prison(), arena, new MockRpcClient());
await handler.SignTransactionAsync(req, CancellationToken.None);
- Assert.True(round.CoinjoinState.AssertSigning().IsFullySigned);
+ Assert.True(round.Assert<SigningState>().IsFullySigned);
await arena.StopAsync(CancellationToken.None);
}
@@ -84,12 +84,12 @@ namespace WalletWasabi.Tests.UnitTests.WabiSabi.Backend.PostRequests
Alice alice2 = WabiSabiFactory.CreateAlice(key: key2);
round.Alices.Add(alice1);
round.Alices.Add(alice2);
- round.CoinjoinState = round.CoinjoinState.AssertConstruction().AddInput(alice1.Coin).AddInput(alice2.Coin).Finalize();
+ round.CoinjoinState = round.Assert<ConstructionState>().AddInput(alice1.Coin).AddInput(alice2.Coin).Finalize();
round.SetPhase(Phase.TransactionSigning);
using Arena arena = await WabiSabiFactory.CreateAndStartArenaAsync(cfg, round);
// Submit the signature for the second alice to the first alice's input.
- var alice2signedCoinJoin = round.CoinjoinState.AssertSigning().CreateUnsignedTransaction();
+ var alice2signedCoinJoin = round.Assert<SigningState>().CreateUnsignedTransaction();
alice2signedCoinJoin.Sign(key2.GetBitcoinSecret(Network.Main), alice2.Coin);
var req = new TransactionSignaturesRequest(round.Id, new[] { new InputWitnessPair(0, alice2signedCoinJoin.Inputs[0].WitScript) });

MODIFY
WalletWasabi.Tests/UnitTests/WabiSabi/Client/ArenaClientTests.cs
WalletWasabi.Tests/UnitTests/WabiSabi/Client/ArenaClientTests.cs
@@ -133,7 +133,7 @@ namespace WalletWasabi.Tests.UnitTests.WabiSabi.Client
await arena.TriggerAndWaitRoundAsync(TimeSpan.FromMinutes(1));
Assert.Equal(Phase.TransactionSigning, round.Phase);
- var tx = round.CoinjoinState.AssertSigning().CreateTransaction();
+ var tx = round.Assert<SigningState>().CreateTransaction();
Assert.Single(tx.Inputs);
Assert.Equal(2, tx.Outputs.Count);
}
@@ -189,7 +189,7 @@ namespace WalletWasabi.Tests.UnitTests.WabiSabi.Client
round.SetPhase(Phase.TransactionSigning);
- var emptyState = round.CoinjoinState.AssertConstruction();
+ var emptyState = round.Assert<ConstructionState>();
// We can't use ``emptyState.Finalize()` because this is not a valid transaction so we fake it
var finalizedEmptyState = new SigningState(emptyState);
@@ -212,20 +212,20 @@ namespace WalletWasabi.Tests.UnitTests.WabiSabi.Client
await Assert.ThrowsAsync<InvalidOperationException>(async () =>
await apiClient.SignTransactionAsync(round.Id, alice1.Coin, new BitcoinSecret(key2, Network.Main), twoInputs.CreateUnsignedTransaction(), CancellationToken.None));
- Assert.False(round.CoinjoinState.AssertSigning().IsFullySigned);
+ Assert.False(round.Assert<SigningState>().IsFullySigned);
- var unsigned = round.CoinjoinState.AssertSigning().CreateUnsignedTransaction();
+ var unsigned = round.Assert<SigningState>().CreateUnsignedTransaction();
await apiClient.SignTransactionAsync(round.Id, alice1.Coin, new BitcoinSecret(key1, Network.Main), unsigned, CancellationToken.None);
- Assert.True(round.CoinjoinState.AssertSigning().IsInputSigned(alice1.Coin.Outpoint));
- Assert.False(round.CoinjoinState.AssertSigning().IsInputSigned(alice2.Coin.Outpoint));
+ Assert.True(round.Assert<SigningState>().IsInputSigned(alice1.Coin.Outpoint));
+ Assert.False(round.Assert<SigningState>().IsInputSigned(alice2.Coin.Outpoint));
- Assert.False(round.CoinjoinState.AssertSigning().IsFullySigned);
+ Assert.False(round.Assert<SigningState>().IsFullySigned);
await apiClient.SignTransactionAsync(round.Id, alice2.Coin, new BitcoinSecret(key2, Network.Main), unsigned, CancellationToken.None);
- Assert.True(round.CoinjoinState.AssertSigning().IsInputSigned(alice2.Coin.Outpoint));
+ Assert.True(round.Assert<SigningState>().IsInputSigned(alice2.Coin.Outpoint));
- Assert.True(round.CoinjoinState.AssertSigning().IsFullySigned);
+ Assert.True(round.Assert<SigningState>().IsFullySigned);
}
}
}

MODIFY
WalletWasabi/WabiSabi/Backend/PostRequests/InputRegistrationHandler.cs
WalletWasabi/WabiSabi/Backend/PostRequests/InputRegistrationHandler.cs
@@ -75,7 +75,7 @@ namespace WalletWasabi.WabiSabi.Backend.PostRequests
// Compute but don't commit updated CoinJoin to round state, it will
// be re-calculated on input confirmation. This is computed it here
// for validation purposes.
- round.CoinjoinState.AssertConstruction().AddInput(coin);
+ round.Assert<ConstructionState>().AddInput(coin);
var coinJoinInputCommitmentData = new CoinJoinInputCommitmentData("CoinJoinCoordinatorIdentifier", round.Id);
if (!OwnershipProof.VerifyCoinJoinInputProof(ownershipProof, coin.TxOut.ScriptPubKey, coinJoinInputCommitmentData))

MODIFY
WalletWasabi/WabiSabi/Backend/Rounds/Arena.cs
WalletWasabi/WabiSabi/Backend/Rounds/Arena.cs
@@ -119,7 +119,7 @@ namespace WalletWasabi.WabiSabi.Backend.Rounds
var diff = aliceSum - bobSum;
if (diff == 0 || round.OutputRegistrationStart + round.OutputRegistrationTimeout < DateTimeOffset.UtcNow)
{
- var coinjoin = round.CoinjoinState.AssertConstruction();
+ var coinjoin = round.Assert<ConstructionState>();
round.LogInfo($"{coinjoin.Inputs.Count} inputs were added.");
round.LogInfo($"{coinjoin.Outputs.Count} outputs were added.");
@@ -144,7 +144,7 @@ namespace WalletWasabi.WabiSabi.Backend.Rounds
{
foreach (var round in Rounds.Values.Where(x => x.Phase == Phase.TransactionSigning).ToArray())
{
- var state = round.CoinjoinState.AssertSigning();
+ var state = round.Assert<SigningState>();
try
{
@@ -190,7 +190,7 @@ namespace WalletWasabi.WabiSabi.Backend.Rounds
private async Task FailTransactionSigningPhaseAsync(Round round)
{
- var state = round.CoinjoinState.AssertSigning();
+ var state = round.Assert<SigningState>();
var unsignedPrevouts = state.UnsignedInputs.ToHashSet();
@@ -322,7 +322,7 @@ namespace WalletWasabi.WabiSabi.Backend.Rounds
}
else if (round.Phase == Phase.ConnectionConfirmation)
{
- var state = round.CoinjoinState.AssertConstruction();
+ var state = round.Assert<ConstructionState>();
// Ensure the input can be added to the CoinJoin
state = state.AddInput(alice.Coin);
@@ -405,7 +405,7 @@ namespace WalletWasabi.WabiSabi.Backend.Rounds
throw new WabiSabiProtocolException(WabiSabiProtocolErrorCode.WrongPhase, $"Round ({request.RoundId}): Wrong phase ({round.Phase}).");
}
- var state = round.CoinjoinState.AssertSigning();
+ var state = round.Assert<SigningState>();
foreach (var inputWitnessPair in request.InputWitnessPairs)
{
state = state.AddWitness((int)inputWitnessPair.InputIndex, inputWitnessPair.Witness);

MODIFY
WalletWasabi/WabiSabi/Backend/Rounds/Round.cs
WalletWasabi/WabiSabi/Backend/Rounds/Round.cs
@@ -65,6 +65,13 @@ namespace WalletWasabi.WabiSabi.Backend.Rounds
private RoundParameters RoundParameters { get; }
+ public TState Assert<TState>() where TState : MultipartyTransactionState =>
+ CoinjoinState switch
+ {
+ TState s => s,
+ _ => throw new InvalidOperationException($"{typeof(TState).Name} state was expected but {CoinjoinState.GetType().Name} state was received.")
+ };
+
public void SetPhase(Phase phase)
{
if (!Enum.IsDefined<Phase>(phase))
@@ -121,13 +128,13 @@ namespace WalletWasabi.WabiSabi.Backend.Rounds
}
public ConstructionState AddInput(Coin coin)
- => CoinjoinState.AssertConstruction().AddInput(coin);
+ => Assert<ConstructionState>().AddInput(coin);
public ConstructionState AddOutput(TxOut output)
- => CoinjoinState.AssertConstruction().AddOutput(output);
+ => Assert<ConstructionState>().AddOutput(output);
public SigningState AddWitness(int index, WitScript witness)
- => CoinjoinState.AssertSigning().AddWitness(index, witness);
+ => Assert<SigningState>().AddWitness(index, witness);
private uint256 CalculateHash()
=> StrobeHasher.Create(ProtocolConstants.RoundStrobeDomain)

MODIFY
WalletWasabi/WabiSabi/Models/MultipartyTransaction/MultipartyTransactionState.cs
WalletWasabi/WabiSabi/Models/MultipartyTransaction/MultipartyTransactionState.cs
using NBitcoin;
+using System;
using System.Collections.Immutable;
using System.Linq;

DELETE
WalletWasabi/WabiSabi/Models/MultipartyTransaction/MultipartyTransactionStateExtensions.cs
-namespace WalletWasabi.WabiSabi.Models.MultipartyTransaction
-{
- public static class MultipartyTransactionStateExtensions
- {
- public static ConstructionState AssertConstruction(this MultipartyTransactionState state) => (ConstructionState)state;
- public static SigningState AssertSigning(this MultipartyTransactionState state) => (SigningState)state;
- }
-}
\ No newline at end of file

Remove `MultipartyTransactionStateExtensions`