Cleanup transaction.Clone();
MODIFY
WalletWasabi.Backend/Controllers/ChaumianCoinJoinController.cs
WalletWasabi.Backend/Controllers/ChaumianCoinJoinController.cs
@@ -618,7 +618,15 @@ namespace WalletWasabi.Backend.Controllers
{
case RoundPhase.Signing:
{
- return Ok(round.GetUnsignedCoinJoinHex());
+ var hex = round.UnsignedCoinJoinHex;
+ if (hex is { })
+ {
+ return Ok(hex);
+ }
+ else
+ {
+ return NotFound("Hex not found. Thi is impossible.");
+ }
}
default:
{
@@ -689,21 +697,21 @@ namespace WalletWasabi.Backend.Controllers
{
return BadRequest($"Malformed witness is provided. Details: {ex.Message}");
}
- int maxIndex = round.UnsignedCoinJoin.Inputs.Count - 1;
+ int maxIndex = round.CoinJoin.Inputs.Count - 1;
if (maxIndex < index)
{
return BadRequest($"Index out of range. Maximum value: {maxIndex}. Provided value: {index}");
}
// Check duplicates.
- if (round.SignedCoinJoin.Inputs[index].HasWitScript())
+ if (round.CoinJoin.Inputs[index].HasWitScript())
{
return BadRequest("Input is already signed.");
}
// Verify witness.
// 1. Copy UnsignedCoinJoin.
- Transaction cjCopy = Transaction.Parse(round.UnsignedCoinJoin.ToHex(), Network);
+ Transaction cjCopy = Transaction.Parse(round.CoinJoin.ToHex(), Network);
// 2. Sign the copy.
cjCopy.Inputs[index].WitScript = witness;
// 3. Convert the current input to IndexedTxIn.
@@ -717,7 +725,7 @@ namespace WalletWasabi.Backend.Controllers
}
// Finally add it to our CJ.
- round.SignedCoinJoin.Inputs[index].WitScript = witness;
+ round.CoinJoin.Inputs[index].WitScript = witness;
}
alice.State = AliceState.SignedCoinJoin;

MODIFY
WalletWasabi/CoinJoin/Coordinator/Coordinator.cs
WalletWasabi/CoinJoin/Coordinator/Coordinator.cs
@@ -295,13 +295,13 @@ namespace WalletWasabi.CoinJoin.Coordinator
{
using (await CoinJoinsLock.LockAsync().ConfigureAwait(false))
{
- uint256 coinJoinHash = round.SignedCoinJoin.GetHash();
+ uint256 coinJoinHash = round.CoinJoin.GetHash();
CoinJoins.Add(coinJoinHash);
UnconfirmedCoinJoins.Add(coinJoinHash);
await File.AppendAllLinesAsync(CoinJoinsFilePath, new[] { coinJoinHash.ToString() }).ConfigureAwait(false);
// When a round succeeded, adjust the denomination as to users still be able to register with the latest round's active output amount.
- IEnumerable<(Money value, int count)> outputs = round.SignedCoinJoin.GetIndistinguishableOutputs(includeSingle: true);
+ IEnumerable<(Money value, int count)> outputs = round.CoinJoin.GetIndistinguishableOutputs(includeSingle: true);
var bestOutput = outputs.OrderByDescending(x => x.count).FirstOrDefault();
if (bestOutput != default)
{

MODIFY
WalletWasabi/CoinJoin/Coordinator/Rounds/CoordinatorRound.cs
WalletWasabi/CoinJoin/Coordinator/Rounds/CoordinatorRound.cs
@@ -49,21 +49,11 @@ namespace WalletWasabi.CoinJoin.Coordinator.Rounds
public Money FeePerInputs { get; private set; }
public Money FeePerOutputs { get; private set; }
- public Transaction UnsignedCoinJoin { get; private set; }
- private string _unsignedCoinJoinHex;
+ public string UnsignedCoinJoinHex { get; private set; }
public MixingLevelCollection MixingLevels { get; }
- public string GetUnsignedCoinJoinHex()
- {
- if (_unsignedCoinJoinHex is null)
- {
- _unsignedCoinJoinHex = UnsignedCoinJoin.ToHex();
- }
- return _unsignedCoinJoinHex;
- }
-
- public Transaction SignedCoinJoin { get; private set; }
+ public Transaction CoinJoin { get; private set; }
private List<Alice> Alices { get; }
private List<Bob> Bobs { get; } // Do not make it a hashset or do not make Bob IEquitable!!!
@@ -190,10 +180,7 @@ namespace WalletWasabi.CoinJoin.Coordinator.Rounds
MixingLevels.AddNewLevel();
}
- _unsignedCoinJoinHex = null;
-
- UnsignedCoinJoin = null;
- SignedCoinJoin = null;
+ CoinJoin = null;
Alices = new List<Alice>();
Bobs = new List<Bob>();
@@ -402,28 +389,20 @@ namespace WalletWasabi.CoinJoin.Coordinator.Rounds
transaction.Outputs.AddWithOptimize(coordinatorFee, coordinatorAddress);
}
- // https://github.com/zkSNACKs/WalletWasabi/pull/2899#issuecomment-569039237
- // 7. Create the unsigned transaction.
- UnsignedCoinJoin = transaction.Clone();
- //var builder = Network.CreateTransactionBuilder();
- //UnsignedCoinJoin = builder
- // .ContinueToBuild(transaction)
- // .AddCoins(spentCoins) // It makes sure the UnsignedCoinJoin goes through TransactionBuilder optimizations.
- // .BuildTransaction(false);
-
- // 8. Try optimize fees.
- await TryOptimizeFeesAsync(spentCoins).ConfigureAwait(false);
+ // 7. Try optimize fees.
+ await TryOptimizeFeesAsync(transaction, spentCoins).ConfigureAwait(false);
- // 9. Shuffle.
- UnsignedCoinJoin.Inputs.Shuffle();
- UnsignedCoinJoin.Outputs.Shuffle();
+ // 8. Shuffle.
+ transaction.Inputs.Shuffle();
+ transaction.Outputs.Shuffle();
- // 10. Sort inputs and outputs by amount so the coinjoin looks better in a block explorer.
- UnsignedCoinJoin.Inputs.SortByAmount(spentCoins);
- UnsignedCoinJoin.Outputs.SortByAmount();
+ // 9. Sort inputs and outputs by amount so the coinjoin looks better in a block explorer.
+ transaction.Inputs.SortByAmount(spentCoins);
+ transaction.Outputs.SortByAmount();
//Note: We shuffle then sort because inputs and outputs could have equal values
- SignedCoinJoin = Transaction.Parse(UnsignedCoinJoin.ToHex(), Network);
+ CoinJoin = transaction;
+ UnsignedCoinJoinHex = transaction.ToHex();
Phase = RoundPhase.Signing;
}
@@ -716,16 +695,16 @@ namespace WalletWasabi.CoinJoin.Coordinator.Rounds
return acceptedBlindedOutputScriptsCount;
}
- private async Task TryOptimizeFeesAsync(IEnumerable<Coin> spentCoins)
+ private async Task TryOptimizeFeesAsync(Transaction transaction, IEnumerable<Coin> spentCoins)
{
try
{
await TryOptimizeConfirmationTargetAsync(spentCoins.Select(x => x.Outpoint.Hash).ToHashSet()).ConfigureAwait(false);
// 8.1. Estimate the current FeeRate. Note, there are no signatures yet!
- int estimatedSigSizeBytes = UnsignedCoinJoin.Inputs.Count * Constants.P2wpkhInputSizeInBytes;
- int estimatedFinalTxSize = UnsignedCoinJoin.GetSerializedSize() + estimatedSigSizeBytes;
- Money fee = UnsignedCoinJoin.GetFee(spentCoins.ToArray());
+ int estimatedSigSizeBytes = transaction.Inputs.Count * Constants.P2wpkhInputSizeInBytes;
+ int estimatedFinalTxSize = transaction.GetSerializedSize() + estimatedSigSizeBytes;
+ Money fee = transaction.GetFee(spentCoins.ToArray());
// There is a currentFeeRate null check later.
FeeRate currentFeeRate = null;
@@ -763,9 +742,9 @@ namespace WalletWasabi.CoinJoin.Coordinator.Rounds
Money toSave = fee - feeShouldBePaid;
// 8.2.2. Get the outputs to divide the savings between.
- int maxMixCount = UnsignedCoinJoin.GetIndistinguishableOutputs(includeSingle: true).Max(x => x.count);
- Money bestMixAmount = UnsignedCoinJoin.GetIndistinguishableOutputs(includeSingle: true).Where(x => x.count == maxMixCount).Max(x => x.value);
- int bestMixCount = UnsignedCoinJoin.GetIndistinguishableOutputs(includeSingle: true).First(x => x.value == bestMixAmount).count;
+ int maxMixCount = transaction.GetIndistinguishableOutputs(includeSingle: true).Max(x => x.count);
+ Money bestMixAmount = transaction.GetIndistinguishableOutputs(includeSingle: true).Where(x => x.count == maxMixCount).Max(x => x.value);
+ int bestMixCount = transaction.GetIndistinguishableOutputs(includeSingle: true).First(x => x.value == bestMixAmount).count;
// 8.2.3. Get the savings per best mix outputs.
long toSavePerBestMixOutputs = toSave.Satoshi / bestMixCount;
@@ -773,7 +752,7 @@ namespace WalletWasabi.CoinJoin.Coordinator.Rounds
// 8.2.4. Modify the best mix outputs in the transaction.
if (toSavePerBestMixOutputs > 0)
{
- foreach (TxOut output in UnsignedCoinJoin.Outputs.Where(x => x.Value == bestMixAmount))
+ foreach (TxOut output in transaction.Outputs.Where(x => x.Value == bestMixAmount))
{
output.Value += toSavePerBestMixOutputs;
}
@@ -1085,35 +1064,35 @@ namespace WalletWasabi.CoinJoin.Coordinator.Rounds
using (await RoundSynchronizerLock.LockAsync().ConfigureAwait(false))
{
// Check if fully signed.
- if (SignedCoinJoin.Inputs.All(x => x.HasWitScript()))
+ if (CoinJoin.Inputs.All(x => x.HasWitScript()))
{
Logger.LogInfo($"Round ({RoundId}): Trying to broadcast coinjoin.");
try
{
Coin[] spentCoins = Alices.SelectMany(x => x.Inputs).ToArray();
- Money networkFee = SignedCoinJoin.GetFee(spentCoins);
+ Money networkFee = CoinJoin.GetFee(spentCoins);
Logger.LogInfo($"Round ({RoundId}): Network Fee: {networkFee.ToString(false, false)} BTC.");
- Logger.LogInfo($"Round ({RoundId}): Coordinator Fee: {SignedCoinJoin.Outputs.SingleOrDefault(x => x.ScriptPubKey == Constants.GetCoordinatorAddress(Network).ScriptPubKey)?.Value?.ToString(false, false) ?? "0"} BTC.");
- FeeRate feeRate = SignedCoinJoin.GetFeeRate(spentCoins);
+ Logger.LogInfo($"Round ({RoundId}): Coordinator Fee: {CoinJoin.Outputs.SingleOrDefault(x => x.ScriptPubKey == Constants.GetCoordinatorAddress(Network).ScriptPubKey)?.Value?.ToString(false, false) ?? "0"} BTC.");
+ FeeRate feeRate = CoinJoin.GetFeeRate(spentCoins);
Logger.LogInfo($"Round ({RoundId}): Network Fee Rate: {feeRate.FeePerK.ToDecimal(MoneyUnit.Satoshi) / 1000} sat/vByte.");
- Logger.LogInfo($"Round ({RoundId}): Number of inputs: {SignedCoinJoin.Inputs.Count}.");
- Logger.LogInfo($"Round ({RoundId}): Number of outputs: {SignedCoinJoin.Outputs.Count}.");
- Logger.LogInfo($"Round ({RoundId}): Serialized Size: {SignedCoinJoin.GetSerializedSize() / 1024} KB.");
- Logger.LogInfo($"Round ({RoundId}): VSize: {SignedCoinJoin.GetVirtualSize() / 1024} KB.");
- foreach (var o in SignedCoinJoin.GetIndistinguishableOutputs(includeSingle: false))
+ Logger.LogInfo($"Round ({RoundId}): Number of inputs: {CoinJoin.Inputs.Count}.");
+ Logger.LogInfo($"Round ({RoundId}): Number of outputs: {CoinJoin.Outputs.Count}.");
+ Logger.LogInfo($"Round ({RoundId}): Serialized Size: {CoinJoin.GetSerializedSize() / 1024} KB.");
+ Logger.LogInfo($"Round ({RoundId}): VSize: {CoinJoin.GetVirtualSize() / 1024} KB.");
+ foreach (var o in CoinJoin.GetIndistinguishableOutputs(includeSingle: false))
{
Logger.LogInfo($"Round ({RoundId}): There are {o.count} occurrences of {o.value.ToString(true, false)} BTC output.");
}
- await RpcClient.SendRawTransactionAsync(SignedCoinJoin).ConfigureAwait(false);
- broadcasted = SignedCoinJoin;
+ await RpcClient.SendRawTransactionAsync(CoinJoin).ConfigureAwait(false);
+ broadcasted = CoinJoin;
Succeed(syncLock: false);
- Logger.LogInfo($"Round ({RoundId}): Successfully broadcasted the CoinJoin: {SignedCoinJoin.GetHash()}.");
+ Logger.LogInfo($"Round ({RoundId}): Successfully broadcasted the CoinJoin: {CoinJoin.GetHash()}.");
}
catch (Exception ex)
{
- Abort($"Could not broadcast the CoinJoin: {SignedCoinJoin.GetHash()}.", syncLock: false);
+ Abort($"Could not broadcast the CoinJoin: {CoinJoin.GetHash()}.", syncLock: false);
Logger.LogError(ex);
}
}

Cleanup transaction.Clone();