Make IsLikelyOwnCoinJoin nullable and not required.
MODIFY
WalletWasabi.Tests/IntegrationTests/RegTests.cs
WalletWasabi.Tests/IntegrationTests/RegTests.cs
@@ -2106,7 +2106,7 @@ namespace WalletWasabi.Tests.IntegrationTests
// Test DelayedClientRoundRegistration logic.
ClientRoundRegistration first = null;
var second = new ClientRoundRegistration(aliceClient,
- new[] { new SmartCoin(uint256.One, 1, Script.Empty, Money.Zero, new[] { new TxoRef(uint256.One, 1) }, Height.Unknown, true, 2, isLikelyCoinJoinOutput: false) },
+ new[] { new SmartCoin(uint256.One, 1, Script.Empty, Money.Zero, new[] { new TxoRef(uint256.One, 1) }, Height.Unknown, true, 2) },
BitcoinAddress.Create("12Rty3c8j3QiZSwLVaBtch6XUMZaja3RC7", Network.Main));
first = second;
second = null;
@@ -3168,7 +3168,7 @@ namespace WalletWasabi.Tests.IntegrationTests
var height = await rpc.GetBlockCountAsync();
var bechCoin = tx.Outputs.GetCoins(bech.ScriptPubKey).Single();
- var smartCoin = new SmartCoin(bechCoin, tx.Inputs.Select(x => new TxoRef(x.PrevOut)).ToArray(), height + 1, replaceable: false, anonymitySet: tx.GetAnonymitySet(bechCoin.Outpoint.N), isLikelyCoinJoinOutput: false);
+ var smartCoin = new SmartCoin(bechCoin, tx.Inputs.Select(x => new TxoRef(x.PrevOut)).ToArray(), height + 1, replaceable: false, anonymitySet: tx.GetAnonymitySet(bechCoin.Outpoint.N));
var chaumianClient = new CoinJoinClient(synchronizer, rpc.Network, keyManager, new Uri(RegTestFixture.BackendEndPoint), null);
@@ -3275,10 +3275,10 @@ namespace WalletWasabi.Tests.IntegrationTests
var bech3Coin = tx3.Outputs.GetCoins(bech3.ScriptPubKey).Single();
var bech4Coin = tx4.Outputs.GetCoins(bech4.ScriptPubKey).Single();
- var smartCoin1 = new SmartCoin(bech1Coin, tx1.Inputs.Select(x => new TxoRef(x.PrevOut)).ToArray(), height, replaceable: false, anonymitySet: tx1.GetAnonymitySet(bech1Coin.Outpoint.N), isLikelyCoinJoinOutput: false);
- var smartCoin2 = new SmartCoin(bech2Coin, tx2.Inputs.Select(x => new TxoRef(x.PrevOut)).ToArray(), height, replaceable: false, anonymitySet: tx2.GetAnonymitySet(bech2Coin.Outpoint.N), isLikelyCoinJoinOutput: false);
- var smartCoin3 = new SmartCoin(bech3Coin, tx3.Inputs.Select(x => new TxoRef(x.PrevOut)).ToArray(), height, replaceable: false, anonymitySet: tx3.GetAnonymitySet(bech3Coin.Outpoint.N), isLikelyCoinJoinOutput: false);
- var smartCoin4 = new SmartCoin(bech4Coin, tx4.Inputs.Select(x => new TxoRef(x.PrevOut)).ToArray(), height, replaceable: false, anonymitySet: tx4.GetAnonymitySet(bech4Coin.Outpoint.N), isLikelyCoinJoinOutput: false);
+ var smartCoin1 = new SmartCoin(bech1Coin, tx1.Inputs.Select(x => new TxoRef(x.PrevOut)).ToArray(), height, replaceable: false, anonymitySet: tx1.GetAnonymitySet(bech1Coin.Outpoint.N));
+ var smartCoin2 = new SmartCoin(bech2Coin, tx2.Inputs.Select(x => new TxoRef(x.PrevOut)).ToArray(), height, replaceable: false, anonymitySet: tx2.GetAnonymitySet(bech2Coin.Outpoint.N));
+ var smartCoin3 = new SmartCoin(bech3Coin, tx3.Inputs.Select(x => new TxoRef(x.PrevOut)).ToArray(), height, replaceable: false, anonymitySet: tx3.GetAnonymitySet(bech3Coin.Outpoint.N));
+ var smartCoin4 = new SmartCoin(bech4Coin, tx4.Inputs.Select(x => new TxoRef(x.PrevOut)).ToArray(), height, replaceable: false, anonymitySet: tx4.GetAnonymitySet(bech4Coin.Outpoint.N));
var chaumianClient1 = new CoinJoinClient(synchronizer, rpc.Network, keyManager, new Uri(RegTestFixture.BackendEndPoint), null);
var chaumianClient2 = new CoinJoinClient(synchronizer, rpc.Network, keyManager, new Uri(RegTestFixture.BackendEndPoint), null);
@@ -3301,7 +3301,7 @@ namespace WalletWasabi.Tests.IntegrationTests
Assert.True(smartCoin2.CoinJoinInProgress);
// Make sure it does not throw.
- await chaumianClient1.DequeueCoinsFromMixAsync(new SmartCoin((network.Consensus.ConsensusFactory.CreateTransaction()).GetHash(), 1, new Script(), Money.Parse("3"), new TxoRef[] { new TxoRef((network.Consensus.ConsensusFactory.CreateTransaction()).GetHash(), 0) }, Height.Mempool, replaceable: false, anonymitySet: 1, isLikelyCoinJoinOutput: false), DequeueReason.UserRequested);
+ await chaumianClient1.DequeueCoinsFromMixAsync(new SmartCoin((network.Consensus.ConsensusFactory.CreateTransaction()).GetHash(), 1, new Script(), Money.Parse("3"), new TxoRef[] { new TxoRef((network.Consensus.ConsensusFactory.CreateTransaction()).GetHash(), 0) }, Height.Mempool, replaceable: false, anonymitySet: 1), DequeueReason.UserRequested);
Assert.True(2 == (await chaumianClient1.QueueCoinsToMixAsync(password, smartCoin1, smartCoin2)).Count());
await chaumianClient1.DequeueCoinsFromMixAsync(smartCoin1, DequeueReason.UserRequested);

MODIFY
WalletWasabi.Tests/UnitTests/SmartCoinTests.cs
WalletWasabi.Tests/UnitTests/SmartCoinTests.cs
@@ -25,9 +25,9 @@ namespace WalletWasabi.Tests.UnitTests
var height = Height.Mempool;
var label = "foo";
- var coin = new SmartCoin(txId, index, scriptPubKey, amount, spentOutputs, height, tx.RBF, tx.GetAnonymitySet(index), isLikelyCoinJoinOutput: false, label, txId);
+ var coin = new SmartCoin(txId, index, scriptPubKey, amount, spentOutputs, height, tx.RBF, tx.GetAnonymitySet(index), label, txId);
// If the txId or the index differ, equality should think it's a different coin.
- var differentCoin = new SmartCoin(txId, index + 1, scriptPubKey, amount, spentOutputs, height, tx.RBF, tx.GetAnonymitySet(index + 1), isLikelyCoinJoinOutput: false, label, txId);
+ var differentCoin = new SmartCoin(txId, index + 1, scriptPubKey, amount, spentOutputs, height, tx.RBF, tx.GetAnonymitySet(index + 1), label, txId);
var differentOutput = tx.Outputs[1];
var differentSpentOutputs = new[]
{
@@ -35,7 +35,7 @@ namespace WalletWasabi.Tests.UnitTests
};
// If the txId and the index are the same, equality should think it's the same coin.
- var sameCoin = new SmartCoin(txId, index, differentOutput.ScriptPubKey, differentOutput.Value, differentSpentOutputs, Height.Unknown, tx.RBF, tx.GetAnonymitySet(index), isLikelyCoinJoinOutput: false, "boo", null);
+ var sameCoin = new SmartCoin(txId, index, differentOutput.ScriptPubKey, differentOutput.Value, differentSpentOutputs, Height.Unknown, tx.RBF, tx.GetAnonymitySet(index), "boo", null);
Assert.Equal(coin, sameCoin);
Assert.NotEqual(coin, differentCoin);

MODIFY
WalletWasabi.Tests/UnitTests/Transactions/TransactionFactoryTests.cs
WalletWasabi.Tests/UnitTests/Transactions/TransactionFactoryTests.cs
@@ -610,7 +610,7 @@ namespace WalletWasabi.Tests.UnitTests.Transactions
};
pubKey.SetLabel(slabel);
pubKey.SetKeyState(KeyState.Used);
- return new SmartCoin(RandomUtils.GetUInt256(), (uint)randomIndex(), pubKey.P2wpkhScript, Money.Coins(amount), spentOutput, height, false, anonymitySet, false, slabel, pubKey: pubKey);
+ return new SmartCoin(RandomUtils.GetUInt256(), (uint)randomIndex(), pubKey.P2wpkhScript, Money.Coins(amount), spentOutput, height, false, anonymitySet, slabel, pubKey: pubKey);
}
}
}

MODIFY
WalletWasabi/Blockchain/TransactionOutputs/SmartCoin.cs
WalletWasabi/Blockchain/TransactionOutputs/SmartCoin.cs
@@ -167,7 +167,7 @@ namespace WalletWasabi.Blockchain.TransactionOutputs
private set => RaiseAndSetIfChanged(ref _hdPubKey, value);
}
- public bool IsLikelyCoinJoinOutput { get; private set; }
+ public bool? IsLikelyCoinJoinOutput { get; set; }
/// <summary>
/// It's a secret, so it's usually going to be null. Do not use it.
@@ -250,12 +250,12 @@ namespace WalletWasabi.Blockchain.TransactionOutputs
#region Constructors
- public SmartCoin(uint256 transactionId, uint index, Script scriptPubKey, Money amount, TxoRef[] spentOutputs, Height height, bool replaceable, int anonymitySet, bool isLikelyCoinJoinOutput, SmartLabel label = null, uint256 spenderTransactionId = null, bool coinJoinInProgress = false, DateTimeOffset? bannedUntilUtc = null, bool spentAccordingToBackend = false, HdPubKey pubKey = null)
+ public SmartCoin(uint256 transactionId, uint index, Script scriptPubKey, Money amount, TxoRef[] spentOutputs, Height height, bool replaceable, int anonymitySet, SmartLabel label = null, uint256 spenderTransactionId = null, bool coinJoinInProgress = false, DateTimeOffset? bannedUntilUtc = null, bool spentAccordingToBackend = false, HdPubKey pubKey = null)
{
- Create(transactionId, index, scriptPubKey, amount, spentOutputs, height, replaceable, anonymitySet, isLikelyCoinJoinOutput, label, spenderTransactionId, coinJoinInProgress, bannedUntilUtc, spentAccordingToBackend, pubKey);
+ Create(transactionId, index, scriptPubKey, amount, spentOutputs, height, replaceable, anonymitySet, label, spenderTransactionId, coinJoinInProgress, bannedUntilUtc, spentAccordingToBackend, pubKey);
}
- public SmartCoin(Coin coin, TxoRef[] spentOutputs, Height height, bool replaceable, int anonymitySet, bool isLikelyCoinJoinOutput, SmartLabel label = null, uint256 spenderTransactionId = null, bool coinJoinInProgress = false, DateTimeOffset? bannedUntilUtc = null, bool spentAccordingToBackend = false, HdPubKey pubKey = null)
+ public SmartCoin(Coin coin, TxoRef[] spentOutputs, Height height, bool replaceable, int anonymitySet, SmartLabel label = null, uint256 spenderTransactionId = null, bool coinJoinInProgress = false, DateTimeOffset? bannedUntilUtc = null, bool spentAccordingToBackend = false, HdPubKey pubKey = null)
{
OutPoint outpoint = Guard.NotNull($"{coin}.{coin?.Outpoint}", coin?.Outpoint);
uint256 transactionId = outpoint.Hash;
@@ -263,10 +263,10 @@ namespace WalletWasabi.Blockchain.TransactionOutputs
Script scriptPubKey = Guard.NotNull($"{coin}.{coin?.ScriptPubKey}", coin?.ScriptPubKey);
Money amount = Guard.NotNull($"{coin}.{coin?.Amount}", coin?.Amount);
- Create(transactionId, index, scriptPubKey, amount, spentOutputs, height, replaceable, anonymitySet, isLikelyCoinJoinOutput, label, spenderTransactionId, coinJoinInProgress, bannedUntilUtc, spentAccordingToBackend, pubKey);
+ Create(transactionId, index, scriptPubKey, amount, spentOutputs, height, replaceable, anonymitySet, label, spenderTransactionId, coinJoinInProgress, bannedUntilUtc, spentAccordingToBackend, pubKey);
}
- private void Create(uint256 transactionId, uint index, Script scriptPubKey, Money amount, TxoRef[] spentOutputs, Height height, bool replaceable, int anonymitySet, bool isLikelyCoinJoinOutput, SmartLabel label, uint256 spenderTransactionId, bool coinJoinInProgress, DateTimeOffset? bannedUntilUtc, bool spentAccordingToBackend, HdPubKey pubKey)
+ private void Create(uint256 transactionId, uint index, Script scriptPubKey, Money amount, TxoRef[] spentOutputs, Height height, bool replaceable, int anonymitySet, SmartLabel label, uint256 spenderTransactionId, bool coinJoinInProgress, DateTimeOffset? bannedUntilUtc, bool spentAccordingToBackend, HdPubKey pubKey)
{
TransactionId = Guard.NotNull(nameof(transactionId), transactionId);
Index = Guard.NotNull(nameof(index), index);
@@ -276,7 +276,7 @@ namespace WalletWasabi.Blockchain.TransactionOutputs
SpentOutputs = Guard.NotNullOrEmpty(nameof(spentOutputs), spentOutputs);
IsReplaceable = replaceable;
AnonymitySet = Guard.InRangeAndNotNull(nameof(anonymitySet), anonymitySet, 1, int.MaxValue);
- IsLikelyCoinJoinOutput = isLikelyCoinJoinOutput;
+ IsLikelyCoinJoinOutput = null;
SpenderTransactionId = spenderTransactionId;

MODIFY
WalletWasabi/Blockchain/TransactionProcessing/TransactionProcessor.cs
WalletWasabi/Blockchain/TransactionProcessing/TransactionProcessor.cs
@@ -164,13 +164,6 @@ namespace WalletWasabi.Blockchain.TransactionProcessing
}
}
- if (tx.Transaction.Inputs.Count > 1 // The tx must have more than one input in order to be a coinjoin.
- && tx.Transaction.HasIndistinguishableOutputs() // The tx must have more than one equal output in order to be a coinjoin.
- && Coins.AsAllCoinsView().OutPoints(tx.Transaction.Inputs.ToTxoRefs()).Any()) // If the input is any of our coins, then it's our CJ.
- {
- result.IsLikelyOwnCoinJoin = true;
- }
-
List<SmartCoin> spentOwnCoins = null;
for (var i = 0U; i < tx.Transaction.Outputs.Count; i++)
{
@@ -193,7 +186,7 @@ namespace WalletWasabi.Blockchain.TransactionProcessing
anonset += spentOwnCoins.Min(x => x.AnonymitySet) - 1; // Minus 1, because do not count own.
}
- SmartCoin newCoin = new SmartCoin(txId, i, output.ScriptPubKey, output.Value, tx.Transaction.Inputs.ToTxoRefs().ToArray(), tx.Height, tx.IsRBF, anonset, result.IsLikelyOwnCoinJoin, foundKey.Label, spenderTransactionId: null, false, pubKey: foundKey); // Do not inherit locked status from key, that's different.
+ SmartCoin newCoin = new SmartCoin(txId, i, output.ScriptPubKey, output.Value, tx.Transaction.Inputs.ToTxoRefs().ToArray(), tx.Height, tx.IsRBF, anonset, foundKey.Label, spenderTransactionId: null, false, pubKey: foundKey); // Do not inherit locked status from key, that's different.
result.ReceivedCoins.Add(newCoin);
// If we did not have it.
@@ -225,6 +218,13 @@ namespace WalletWasabi.Blockchain.TransactionProcessing
}
}
+ var isLikelyCj = false;
+ if (tx.Transaction.Inputs.Count > 1 // The tx must have more than one input in order to be a coinjoin.
+ && tx.Transaction.HasIndistinguishableOutputs()) // The tx must have more than one equal output in order to be a coinjoin.
+ {
+ isLikelyCj = true;
+ }
+
foreach (var coin in Coins.AsAllCoinsView())
{
// If spends any of our coin
@@ -245,7 +245,18 @@ namespace WalletWasabi.Blockchain.TransactionProcessing
{
result.NewlyConfirmedSpentCoins.Add(coin);
}
+
+ if (isLikelyCj)
+ {
+ result.IsLikelyOwnCoinJoin = true;
+ }
+ }
}
+
+ foreach (var coin in result.ReceivedCoins)
+ {
+ // May be too late to set it at this point. It'd be better to set at the constructor, but couldn't find a way without ruining performance.
+ coin.IsLikelyCoinJoinOutput = result.IsLikelyOwnCoinJoin;
}
if (result.IsNews)

MODIFY
WalletWasabi/Blockchain/Transactions/TransactionFactory.cs
WalletWasabi/Blockchain/Transactions/TransactionFactory.cs
@@ -261,7 +261,7 @@ namespace WalletWasabi.Blockchain.Transactions
TxOut output = tx.Outputs[i];
var anonset = tx.GetAnonymitySet(i) + spentCoins.Min(x => x.AnonymitySet) - 1; // Minus 1, because count own only once.
var foundKey = KeyManager.GetKeyForScriptPubKey(output.ScriptPubKey);
- var coin = new SmartCoin(tx.GetHash(), i, output.ScriptPubKey, output.Value, tx.Inputs.ToTxoRefs().ToArray(), Height.Unknown, tx.RBF, anonset, isLikelyCoinJoinOutput: false, pubKey: foundKey);
+ var coin = new SmartCoin(tx.GetHash(), i, output.ScriptPubKey, output.Value, tx.Inputs.ToTxoRefs().ToArray(), Height.Unknown, tx.RBF, anonset, pubKey: foundKey);
label = SmartLabel.Merge(label, coin.Label); // foundKey's label is already added to the coinlabel.
if (foundKey is null)

MODIFY
WalletWasabi/CoinJoin/Client/Rounds/ClientState.cs
WalletWasabi/CoinJoin/Client/Rounds/ClientState.cs
@@ -198,7 +198,7 @@ namespace WalletWasabi.CoinJoin.Client.Rounds
Func<SmartCoin, bool> confirmationPredicate;
if (allowUnconfirmedZeroLink)
{
- confirmationPredicate = x => x.Confirmed || x.IsLikelyCoinJoinOutput;
+ confirmationPredicate = x => x.Confirmed || x.IsLikelyCoinJoinOutput is true;
}
else
{

Make IsLikelyOwnCoinJoin nullable and not required.