Refine CcjClient
MODIFY
WalletWasabi.Tests/RegTests.cs
WalletWasabi.Tests/RegTests.cs
@@ -1640,6 +1640,7 @@ namespace WalletWasabi.Tests
Assert.NotNull(inputsResponse.BlindedOutputSignature);
Assert.NotEqual(Guid.Empty, inputsResponse.UniqueId);
Assert.True(inputsResponse.RoundId > 0);
+
long roundId = inputsResponse.RoundId;
Assert.Null(await aliceClient.PostConfirmationAsync(inputsResponse.RoundId, inputsResponse.UniqueId));
@@ -1882,8 +1883,14 @@ namespace WalletWasabi.Tests
inputsResponse = await aliceClient.PostInputsAsync(new Key().ScriptPubKey, blinded2.BlindedData, new InputProofModel { Input = input2, Proof = key2.SignMessage(blindedOutputScriptHex2) });
Guid uniqueAliceId2 = inputsResponse.UniqueId;
Assert.Equal(roundId, inputsResponse.RoundId);
+
byte[] unblindedSignature2 = blindingKey.PubKey.UnblindSignature(inputsResponse.BlindedOutputSignature, blinded2.BlindingFactor);
+ if (!blindingKey.PubKey.Verify(unblindedSignature2, outputAddress2.ScriptPubKey.ToBytes()))
+ {
+ throw new NotSupportedException("Coordinator did not sign the blinded output properly.");
+ }
+
var roundHash = await aliceClient.PostConfirmationAsync(roundId, uniqueAliceId1);
Assert.Equal(roundHash, await aliceClient.PostConfirmationAsync(roundId, uniqueAliceId1)); // Make sure it won't throw error for double confirming.
Assert.Equal(roundHash, await aliceClient.PostConfirmationAsync(roundId, uniqueAliceId2));
@@ -2174,7 +2181,7 @@ namespace WalletWasabi.Tests
Money denomination = new Money(0.1m, MoneyUnit.BTC);
decimal coordinatorFeePercent = 0.1m;
int anonymitySet = 2;
- int connectionConfirmationTimeout = 60;
+ int connectionConfirmationTimeout = 14;
var roundConfig = new CcjRoundConfig(denomination, 140, coordinatorFeePercent, anonymitySet, 240, connectionConfirmationTimeout, 50, 50, 1);
coordinator.UpdateRoundConfig(roundConfig);
coordinator.FailAllRoundsInInputRegistration();
@@ -2182,46 +2189,87 @@ namespace WalletWasabi.Tests
var keyManager = KeyManager.CreateNew(out _, password);
var key1 = keyManager.GenerateNewKey("foo", KeyState.Clean, false);
var key2 = keyManager.GenerateNewKey("bar", KeyState.Clean, false);
+ var key3 = keyManager.GenerateNewKey("baz", KeyState.Clean, false);
var bech1 = key1.GetP2wpkhAddress(network);
var bech2 = key2.GetP2wpkhAddress(network);
+ var bech3 = key3.GetP2wpkhAddress(network);
var amount1 = new Money(0.03m, MoneyUnit.BTC);
var amount2 = new Money(0.08m, MoneyUnit.BTC);
+ var amount3 = new Money(0.3m, MoneyUnit.BTC);
var txid1 = await rpc.SendToAddressAsync(bech1, amount1, replaceable: false);
var txid2 = await rpc.SendToAddressAsync(bech2, amount2, replaceable: false);
+ var txid3 = await rpc.SendToAddressAsync(bech3, amount3, replaceable: false);
key1.KeyState = KeyState.Used;
key2.KeyState = KeyState.Used;
+ key3.KeyState = KeyState.Used;
var tx1 = await rpc.GetRawTransactionAsync(txid1);
var tx2 = await rpc.GetRawTransactionAsync(txid2);
+ var tx3 = await rpc.GetRawTransactionAsync(txid3);
await rpc.GenerateAsync(1);
var height = await rpc.GetBlockCountAsync();
var smartCoin1 = new SmartCoin(txid1, tx1.Outputs.GetIndex(bech1.ScriptPubKey), bech1.ScriptPubKey, amount1, tx1.Inputs.Select(x => new TxoRef(x.PrevOut)).ToArray(), height, rbf: false);
var smartCoin2 = new SmartCoin(txid2, tx2.Outputs.GetIndex(bech2.ScriptPubKey), bech2.ScriptPubKey, amount2, tx2.Inputs.Select(x => new TxoRef(x.PrevOut)).ToArray(), height, rbf: false);
+ var smartCoin3 = new SmartCoin(txid3, tx3.Outputs.GetIndex(bech3.ScriptPubKey), bech3.ScriptPubKey, amount3, tx3.Inputs.Select(x => new TxoRef(x.PrevOut)).ToArray(), height, rbf: false);
- var chaumianClient = new CcjClient(Global.RpcClient.Network, Global.Coordinator.RsaKey.PubKey, keyManager, new Uri(RegTestFixture.BackendEndPoint));
+ var chaumianClient1 = new CcjClient(Global.RpcClient.Network, Global.Coordinator.RsaKey.PubKey, keyManager, new Uri(RegTestFixture.BackendEndPoint));
+ var chaumianClient2 = new CcjClient(Global.RpcClient.Network, Global.Coordinator.RsaKey.PubKey, keyManager, new Uri(RegTestFixture.BackendEndPoint));
try
{
- chaumianClient.Start();
+ chaumianClient1.Start();
+ //chaumianClient2.Start();
- Assert.Throws<NotSupportedException>(() => chaumianClient.QueueCoinsToMix(password, smartCoin1, smartCoin2));
smartCoin1.Locked = true;
- smartCoin2.Locked = true;
- Assert.Throws<SecurityException>(() => chaumianClient.QueueCoinsToMix("asdasdasd", smartCoin1, smartCoin2));
+ Assert.True(0 == chaumianClient1.QueueCoinsToMix(password, smartCoin1).Count());
+ Assert.True(smartCoin1.Locked);
- // Make sure it doesn't throw.
- await chaumianClient.DequeueCoinsFromMixAsync((smartCoin1.TransactionId, smartCoin1.Index));
+ Assert.Throws<SecurityException>(() => chaumianClient1.QueueCoinsToMix("asdasdasd", smartCoin1, smartCoin2));
+ Assert.True(smartCoin1.Locked);
+ Assert.False(smartCoin2.Locked);
+ smartCoin1.Locked = false;
+
+ chaumianClient1.QueueCoinsToMix(password, smartCoin1, smartCoin2);
+ Assert.True(smartCoin1.Locked);
+ Assert.True(smartCoin2.Locked);
- Assert.True(2 == chaumianClient.QueueCoinsToMix(password, smartCoin1, smartCoin2).Count());
- await chaumianClient.DequeueCoinsFromMixAsync((smartCoin1.TransactionId, 111));
- await chaumianClient.DequeueCoinsFromMixAsync(smartCoin1, smartCoin2);
- Assert.True(2 == chaumianClient.QueueCoinsToMix(password, smartCoin1, smartCoin2).Count());
- await chaumianClient.DequeueCoinsFromMixAsync(smartCoin1);
- await chaumianClient.DequeueCoinsFromMixAsync(smartCoin2);
+ // Make sure it doesn't throw.
+ await chaumianClient1.DequeueCoinsFromMixAsync(new SmartCoin((new Transaction()).GetHash(), 1, new Script(), Money.Parse("3"), new TxoRef[] { new TxoRef((new Transaction()).GetHash(), 0) }, Height.MemPool, rbf: false));
+
+ Assert.True(2 == chaumianClient1.QueueCoinsToMix(password, smartCoin1, smartCoin2).Count());
+ await chaumianClient1.DequeueCoinsFromMixAsync(smartCoin1);
+ Assert.False(smartCoin1.Locked);
+ await chaumianClient1.DequeueCoinsFromMixAsync(smartCoin1, smartCoin2);
+ Assert.False(smartCoin1.Locked);
+ Assert.False(smartCoin2.Locked);
+ Assert.True(2 == chaumianClient1.QueueCoinsToMix(password, smartCoin1, smartCoin2).Count());
+ Assert.True(smartCoin1.Locked);
+ Assert.True(smartCoin2.Locked);
+ await chaumianClient1.DequeueCoinsFromMixAsync(smartCoin1);
+ await chaumianClient1.DequeueCoinsFromMixAsync(smartCoin2);
+ Assert.False(smartCoin1.Locked);
+ Assert.False(smartCoin2.Locked);
+
+ Assert.True(2 == chaumianClient1.QueueCoinsToMix(password, smartCoin1, smartCoin2).Count());
+ Assert.True(smartCoin1.Locked);
+ Assert.True(smartCoin2.Locked);
+ //smartCoin3.Locked = true;
+ //Assert.True(1 == chaumianClient2.QueueCoinsToMix(password, smartCoin3).Count());
+
+ //Task timeout = Task.Delay(TimeSpan.FromSeconds(connectionConfirmationTimeout * 2 + 7 * 2 + 7 * 2 + 7 * 2));
+ //while ((await rpc.GetRawMempoolAsync()).Length == 0)
+ //{
+ // if(timeout.IsCompletedSuccessfully)
+ // {
+ // throw new TimeoutException("CoinJoin wasn't propagated.");
+ // }
+ // await Task.Delay(1000);
+ //}
}
finally
{
- if (chaumianClient != null)
+ if (chaumianClient1 != null)
{
- await chaumianClient.StopAsync();
+ await chaumianClient1.StopAsync();
+ await chaumianClient2.StopAsync();
}
}
}

MODIFY
WalletWasabi.Tests/SharedFixture.cs
WalletWasabi.Tests/SharedFixture.cs
@@ -32,7 +32,7 @@ namespace WalletWasabi.Tests
// Initialize tests...
Logger.SetFilePath(Path.Combine(DataDir, "Logs.txt"));
- Logger.SetMinimumLevel(LogLevel.Trace);
+ Logger.SetMinimumLevel(LogLevel.Debug);
Logger.SetModes(LogMode.Debug, LogMode.Console, LogMode.File);
}

MODIFY
WalletWasabi/ChaumianCoinJoin/CcjClientRound.cs
WalletWasabi/ChaumianCoinJoin/CcjClientRound.cs
@@ -13,7 +13,7 @@ namespace WalletWasabi.ChaumianCoinJoin
{
public CcjRunningRoundState State { get; set; }
- public List<MixCoin> CoinsRegistered { get; }
+ public List<SmartCoin> CoinsRegistered { get; }
public Guid? AliceUniqueId { get; set; }
@@ -27,7 +27,7 @@ namespace WalletWasabi.ChaumianCoinJoin
public CcjClientRound(CcjRunningRoundState state)
{
State = Guard.NotNull(nameof(state), state);
- CoinsRegistered = new List<MixCoin>();
+ CoinsRegistered = new List<SmartCoin>();
ClearRegistration(); // shortcut for initializing variables
}

MODIFY
WalletWasabi/Models/SmartCoin.cs
WalletWasabi/Models/SmartCoin.cs
@@ -56,6 +56,11 @@ namespace WalletWasabi.Models
public bool SpentOrLocked => SpenderTransactionId != null || Locked;
public bool Confirmed => Height != Height.MemPool && Height != Height.Unknown;
+ /// <summary>
+ /// It's a secret, so it's usually going to be null. Don't use it.
+ /// </summary>
+ public ISecret Secret { get; set; }
+
[JsonConstructor]
public SmartCoin(uint256 transactionId, int index, Script scriptPubKey, Money amount, TxoRef[] spentOutputs, Height height, bool rbf, string label = "", uint256 spenderTransactionId = null, bool locked = false)
{
@@ -69,6 +74,7 @@ namespace WalletWasabi.Models
SpenderTransactionId = spenderTransactionId;
RBF = rbf;
Locked = locked;
+ Secret = null;
}
public Coin GetCoin()

MODIFY
WalletWasabi/Services/CcjClient.cs
WalletWasabi/Services/CcjClient.cs
@@ -22,7 +22,7 @@ namespace WalletWasabi.Services
public class CcjClient
{
public Network Network { get; }
- BlindingRsaPubKey BlindingPubKey { get; }
+ BlindingRsaPubKey CoordinatorPubKey { get; }
public KeyManager KeyManager { get; }
public AliceClient AliceClient { get; }
@@ -31,7 +31,7 @@ namespace WalletWasabi.Services
private AsyncLock MixLock { get; }
- private List<MixCoin> CoinsWaitingForMix { get; }
+ private List<SmartCoin> CoinsWaitingForMix { get; }
private List<CcjClientRound> Rounds { get; }
public event EventHandler<CcjClientRound> RoundAdded;
@@ -50,10 +50,10 @@ namespace WalletWasabi.Services
private CancellationTokenSource Stop { get; }
- public CcjClient(Network network, BlindingRsaPubKey blindingPubKey, KeyManager keyManager, Uri ccjHostUri, IPEndPoint torSocks5EndPoint = null)
+ public CcjClient(Network network, BlindingRsaPubKey coordinatorPubKey, KeyManager keyManager, Uri ccjHostUri, IPEndPoint torSocks5EndPoint = null)
{
Network = Guard.NotNull(nameof(network), network);
- BlindingPubKey = Guard.NotNull(nameof(blindingPubKey), blindingPubKey);
+ CoordinatorPubKey = Guard.NotNull(nameof(coordinatorPubKey), coordinatorPubKey);
KeyManager = Guard.NotNull(nameof(keyManager), keyManager);
AliceClient = new AliceClient(ccjHostUri, torSocks5EndPoint);
BobClient = new BobClient(ccjHostUri, torSocks5EndPoint);
@@ -63,7 +63,7 @@ namespace WalletWasabi.Services
_running = 0;
Stop = new CancellationTokenSource();
_frequentStatusProcessingIfNotMixing = 0;
- CoinsWaitingForMix = new List<MixCoin>();
+ CoinsWaitingForMix = new List<SmartCoin>();
MixLock = new AsyncLock();
}
@@ -111,11 +111,11 @@ namespace WalletWasabi.Services
}
else if (Interlocked.Read(ref _frequentStatusProcessingIfNotMixing) == 1 || inputRegMixing)
{
- double rand = double.Parse($"0.{new Random().Next(2, 8)}"); // randomly between every 0.2 * connConfTimeout and 0.8 * connConfTimeout
+ double rand = double.Parse($"0.{new Random().Next(2, 8)}"); // randomly between every 0.2 * connConfTimeout - 7 and 0.8 * connConfTimeout
int delay;
using (await MixLock.LockAsync())
{
- delay = (int)(rand * Rounds.First(x => x.State.Phase == CcjRoundPhase.InputRegistration).State.RegistrationTimeout);
+ delay = Math.Max(0, (int)(rand * Rounds.First(x => x.State.Phase == CcjRoundPhase.InputRegistration).State.RegistrationTimeout - 7));
}
await Task.Delay(TimeSpan.FromSeconds(delay), Stop.Token);
@@ -175,7 +175,7 @@ namespace WalletWasabi.Services
CcjRunningRoundState state = states.SingleOrDefault(x => x.RoundId == round.State.RoundId);
if (state == null) // The round is not running anymore.
{
- foreach (MixCoin rc in round.CoinsRegistered)
+ foreach (SmartCoin rc in round.CoinsRegistered)
{
CoinsWaitingForMix.Add(rc);
}
@@ -195,21 +195,21 @@ namespace WalletWasabi.Services
using (await MixLock.LockAsync())
{
- CoinsWaitingForMix.RemoveAll(x => x.SmartCoin.SpenderTransactionId != null); // Make sure coins those were somehow spent are removed.
+ CoinsWaitingForMix.RemoveAll(x => x.SpenderTransactionId != null); // Make sure coins those were somehow spent are removed.
CcjClientRound inputRegistrableRound = Rounds.First(x => x.State.Phase == CcjRoundPhase.InputRegistration);
if (inputRegistrableRound.AliceUniqueId == null) // If didn't register already, check what can we register.
{
try
{
- var coinsToRegister = new List<MixCoin>();
+ var coinsToRegister = new List<SmartCoin>();
var amountSoFar = Money.Zero;
Money amountNeededExceptInputFees = inputRegistrableRound.State.Denomination + inputRegistrableRound.State.FeePerOutputs * 2;
var tooSmallInputs = false;
- foreach (MixCoin coin in CoinsWaitingForMix
- .Where(x => x.SmartCoin.Confirmed || x.SmartCoin.Label.Contains("CoinJoin", StringComparison.Ordinal)) // Where our label contains CoinJoin, CoinJoins can be registered even if not confirmed, our label will likely be CoinJoin only if it was a previous CoinJoin, otherwise the server will refuse us.
- .OrderByDescending(y => y.SmartCoin.Amount) // First order by amount.
- .OrderByDescending(z => z.SmartCoin.Confirmed)) // Then order by the amount ordered ienumerable by confirmation, so first try to register confirmed coins.
+ foreach (SmartCoin coin in CoinsWaitingForMix
+ .Where(x => x.Confirmed || x.Label.Contains("CoinJoin", StringComparison.Ordinal)) // Where our label contains CoinJoin, CoinJoins can be registered even if not confirmed, our label will likely be CoinJoin only if it was a previous CoinJoin, otherwise the server will refuse us.
+ .OrderByDescending(y => y.Amount) // First order by amount.
+ .OrderByDescending(z => z.Confirmed)) // Then order by the amount ordered ienumerable by confirmation, so first try to register confirmed coins.
{
coinsToRegister.Add(coin);
if (inputRegistrableRound.State.MaximumInputCountPerPeer < coinsToRegister.Count)
@@ -218,7 +218,7 @@ namespace WalletWasabi.Services
break;
}
- amountSoFar += coin.SmartCoin.Amount;
+ amountSoFar += coin.Amount;
if (amountSoFar > amountNeededExceptInputFees + inputRegistrableRound.State.FeePerInputs * coinsToRegister.Count)
{
break;
@@ -230,21 +230,23 @@ namespace WalletWasabi.Services
{
var changeKey = KeyManager.GenerateNewKey("CoinJoin Change Output", KeyState.Locked, isInternal: true);
var activeKey = KeyManager.GenerateNewKey("CoinJoin Active Output", KeyState.Locked, isInternal: true);
- var blind = BlindingPubKey.Blind(activeKey.GetP2wpkhScript().ToBytes());
+ var blind = CoordinatorPubKey.Blind(activeKey.GetP2wpkhScript().ToBytes());
var inputProofs = new List<InputProofModel>();
- foreach (var coin in coinsToRegister)
+ foreach (SmartCoin coin in coinsToRegister)
{
var inputProof = new InputProofModel
{
- Input = coin.SmartCoin.GetOutPoint(),
+ Input = coin.GetOutPoint(),
Proof = coin.Secret.PrivateKey.SignMessage(ByteHelpers.ToHex(blind.BlindedData))
};
inputProofs.Add(inputProof);
}
InputsResponse inputsResponse = await AliceClient.PostInputsAsync(changeKey.GetP2wpkhScript(), blind.BlindedData, inputProofs.ToArray());
- if (!BlindingPubKey.Verify(inputsResponse.BlindedOutputSignature, blind.BlindedData))
+ byte[] unblindedSignature = CoordinatorPubKey.UnblindSignature(inputsResponse.BlindedOutputSignature, blind.BlindingFactor);
+
+ if (!CoordinatorPubKey.Verify(unblindedSignature, activeKey.GetP2wpkhScript().ToBytes()))
{
throw new NotSupportedException("Coordinator did not sign the blinded output properly.");
}
@@ -266,7 +268,7 @@ namespace WalletWasabi.Services
}
roundRegistered.ActiveOutput = activeKey;
roundRegistered.ChangeOutput = changeKey;
- roundRegistered.UnblindedSignature = BlindingPubKey.UnblindSignature(inputsResponse.BlindedOutputSignature, blind.BlindingFactor);
+ roundRegistered.UnblindedSignature = unblindedSignature;
roundRegistered.AliceUniqueId = inputsResponse.UniqueId;
RoundUpdated?.Invoke(this, roundRegistered);
}
@@ -290,6 +292,14 @@ namespace WalletWasabi.Services
}
catch (Exception ex)
{
+ if(ex.Message.StartsWith("NotFound", StringComparison.Ordinal)) // Alice timed out.
+ {
+ foreach(var coin in inputRegistrableRound.CoinsRegistered)
+ {
+ CoinsWaitingForMix.Add(coin);
+ }
+ inputRegistrableRound.ClearRegistration();
+ }
Logger.LogError<CcjClient>(ex);
}
}
@@ -333,7 +343,7 @@ namespace WalletWasabi.Services
Money amountBack = unsignedCoinJoin.Outputs
.Where(x => x.ScriptPubKey == ongoingRound.ActiveOutput.GetP2wpkhScript() || x.ScriptPubKey == ongoingRound.ChangeOutput.GetP2wpkhScript())
.Sum(y => y.Value);
- Money minAmountBack = ongoingRound.CoinsRegistered.Sum(x => x.SmartCoin.Amount); // Start with input sum.
+ Money minAmountBack = ongoingRound.CoinsRegistered.Sum(x => x.Amount); // Start with input sum.
minAmountBack -= ongoingRound.State.FeePerOutputs * 2 + ongoingRound.State.FeePerInputs * ongoingRound.CoinsRegistered.Count; // Minus miner fee.
Money actualDenomination = unsignedCoinJoin.GetIndistinguishableOutputs().OrderByDescending(x => x.count).First().value; // Denomination may grow.
Money expectedCoordinatorFee = new Money((ongoingRound.State.CoordinatorFeePercent * 0.01m) * decimal.Parse(actualDenomination.ToString(false, true)), MoneyUnit.BTC);
@@ -356,7 +366,7 @@ namespace WalletWasabi.Services
new TransactionBuilder()
.AddKeys(ongoingRound.CoinsRegistered.Select(x => x.Secret).ToArray())
- .AddCoins(ongoingRound.CoinsRegistered.Select(x=>x.SmartCoin.GetCoin()))
+ .AddCoins(ongoingRound.CoinsRegistered.Select(x => x.GetCoin()))
.SignTransactionInPlace(unsignedCoinJoin, SigHash.All);
var myDic = new Dictionary<int, WitScript>();
@@ -364,7 +374,7 @@ namespace WalletWasabi.Services
for (int i = 0; i < unsignedCoinJoin.Inputs.Count; i++)
{
var input = unsignedCoinJoin.Inputs[i];
- if (ongoingRound.CoinsRegistered.Select(x => x.SmartCoin.GetOutPoint()).Contains(input.PrevOut))
+ if (ongoingRound.CoinsRegistered.Select(x => x.GetOutPoint()).Contains(input.PrevOut))
{
myDic.Add(i, unsignedCoinJoin.Inputs[i].WitScript);
}
@@ -375,6 +385,14 @@ namespace WalletWasabi.Services
}
catch (Exception ex)
{
+ if (ex.Message.StartsWith("NotFound", StringComparison.Ordinal)) // Alice timed out.
+ {
+ foreach (var coin in ongoingRound.CoinsRegistered)
+ {
+ CoinsWaitingForMix.Add(coin);
+ }
+ ongoingRound.ClearRegistration();
+ }
Logger.LogError<CcjClient>(ex);
}
}
@@ -411,21 +429,22 @@ namespace WalletWasabi.Services
foreach (SmartCoin coin in coins)
{
- if (coin.SpenderTransactionId != null)
- {
- continue;
- }
- var secret = KeyManager.GetSecrets(password, coin.ScriptPubKey).SingleOrDefault();
- if (secret == null)
+ if (CoinsWaitingForMix.Contains(coin))
{
+ successful.Add(coin);
continue;
}
- if (CoinsWaitingForMix.Select(x => x.SmartCoin).Contains(coin))
+
+ if (coin.SpentOrLocked)
{
continue;
}
- CoinsWaitingForMix.Add(new MixCoin(coin, secret));
+ coin.Secret = KeyManager.GetSecrets(password, coin.ScriptPubKey).Single();
+
+ coin.Locked = true;
+
+ CoinsWaitingForMix.Add(coin);
successful.Add(coin);
}
@@ -433,26 +452,10 @@ namespace WalletWasabi.Services
}
}
- /// <summary>
- /// Unlock coin after dequeuing it.
- /// </summary>
- public async Task DequeueCoinsFromMixAsync(params MixCoin[] coins)
- {
- await DequeueCoinsFromMixAsync(coins.Select(x => (x.SmartCoin.TransactionId, x.SmartCoin.Index)).ToArray());
- }
-
/// <summary>
/// Unlock coin after dequeuing it.
/// </summary>
public async Task DequeueCoinsFromMixAsync(params SmartCoin[] coins)
- {
- await DequeueCoinsFromMixAsync(coins.Select(x => (x.TransactionId, x.Index)).ToArray());
- }
-
- /// <summary>
- /// Unlock coin after dequeuing it.
- /// </summary>
- public async Task DequeueCoinsFromMixAsync(params (uint256 transactionId, int index)[] coins)
{
using (await MixLock.LockAsync())
{
@@ -460,15 +463,17 @@ namespace WalletWasabi.Services
foreach (var coinToDequeue in coins)
{
- MixCoin coinWaitingForMix = CoinsWaitingForMix.SingleOrDefault(x => x.SmartCoin.TransactionId == coinToDequeue.transactionId && x.SmartCoin.Index == coinToDequeue.index);
+ SmartCoin coinWaitingForMix = CoinsWaitingForMix.SingleOrDefault(x => x == coinToDequeue);
if (coinWaitingForMix != null) // If it is not being mixed, we can just remove it.
{
CoinsWaitingForMix.Remove(coinWaitingForMix);
+ coinWaitingForMix.Locked = false;
+ coinWaitingForMix.Secret = null;
}
foreach (CcjClientRound round in Rounds)
{
- MixCoin coinBeingMixed = round.CoinsRegistered.SingleOrDefault(x => x.SmartCoin.TransactionId == coinToDequeue.transactionId && x.SmartCoin.Index == coinToDequeue.index);
+ SmartCoin coinBeingMixed = round.CoinsRegistered.SingleOrDefault(x => x == coinToDequeue);
if (coinBeingMixed != null) // If it is being mixed.
{
@@ -490,7 +495,7 @@ namespace WalletWasabi.Services
}
else
{
- exceptions.Add(new NotSupportedException($"Cannot deque coin in {round.State.Phase} phase. Coin: {coinToDequeue.index}:{coinToDequeue.transactionId}."));
+ exceptions.Add(new NotSupportedException($"Cannot deque coin in {round.State.Phase} phase. Coin: {coinToDequeue.Index}:{coinToDequeue.TransactionId}."));
}
}
}
@@ -527,7 +532,12 @@ namespace WalletWasabi.Services
try
{
// Try to dequeue all coins at last. This should be done manually, and prevent the closing of the software if unsuccessful.
- await DequeueCoinsFromMixAsync(Rounds.SelectMany(x=>x.CoinsRegistered).Concat(CoinsWaitingForMix).ToArray());
+ SmartCoin[] coinsToDequeue;
+ using (MixLock.Lock())
+ {
+ coinsToDequeue = Rounds.SelectMany(x => x.CoinsRegistered).Concat(CoinsWaitingForMix).ToArray();
+ }
+ await DequeueCoinsFromMixAsync(coinsToDequeue);
}
catch (Exception ex)
{

Refine CcjClient