Replace doublespend event with txinfo event
MODIFY
WalletWasabi.Gui/Controls/WalletExplorer/CoinListViewModel.cs
WalletWasabi.Gui/Controls/WalletExplorer/CoinListViewModel.cs
@@ -443,7 +443,7 @@ namespace WalletWasabi.Gui.Controls.WalletExplorer
Observable
.Merge(Observable.FromEventPattern<ReplaceTransactionReceivedEventArgs>(Global.WalletService.TransactionProcessor, nameof(Global.WalletService.TransactionProcessor.ReplaceTransactionReceived)).Select(_ => Unit.Default))
- .Merge(Observable.FromEventPattern<TxCoinsEventArgs>(Global.WalletService.TransactionProcessor, nameof(Global.WalletService.TransactionProcessor.DoubleSpendReceived)).Select(_ => Unit.Default))
+ .Merge(Observable.FromEventPattern<TransactionProcessedResult>(Global.WalletService.TransactionProcessor, nameof(Global.WalletService.TransactionProcessor.WalletRelevantTransactionProcessed)).Select(_ => Unit.Default))
.Merge(Observable.FromEventPattern<TxCoinsEventArgs>(Global.WalletService.TransactionProcessor, nameof(Global.WalletService.TransactionProcessor.CoinsSpent)).Select(_ => Unit.Default))
.Merge(Observable.FromEventPattern<TxCoinsEventArgs>(Global.WalletService.TransactionProcessor, nameof(Global.WalletService.TransactionProcessor.CoinsReceived)).Select(_ => Unit.Default))
.Throttle(TimeSpan.FromSeconds(2)) // Throttle TransactionProcessor events adds/removes.

MODIFY
WalletWasabi.Tests/IntegrationTests/RegTests.cs
WalletWasabi.Tests/IntegrationTests/RegTests.cs
@@ -1475,22 +1475,14 @@ namespace WalletWasabi.Tests.IntegrationTests
srtxwwreq.Transaction = overwriteTx;
var srtxwwres = await rpc.SignRawTransactionWithWalletAsync(srtxwwreq);
- var doubleSpendAwaiter = new EventAwaiter<TxCoinsEventArgs>(
- h => wallet.TransactionProcessor.DoubleSpendReceived += h,
- h => wallet.TransactionProcessor.DoubleSpendReceived -= h);
- await rpc.SendRawTransactionAsync(srtxwwres.SignedTransaction);
- var doubleSpend = await doubleSpendAwaiter.WaitAsync(TimeSpan.FromSeconds(21));
- Assert.Equal(srtxwwres.SignedTransaction.GetHash(), doubleSpend.SmartTransaction.GetHash());
- Assert.Empty(doubleSpend.Coins);
-
- doubleSpendAwaiter = new EventAwaiter<TxCoinsEventArgs>(
- h => wallet.TransactionProcessor.DoubleSpendReceived += h,
- h => wallet.TransactionProcessor.DoubleSpendReceived -= h);
+ var walletRelevantTransactionProcessedAwaiter = new EventAwaiter<TransactionProcessedResult>(
+ h => wallet.TransactionProcessor.WalletRelevantTransactionProcessed += h,
+ h => wallet.TransactionProcessor.WalletRelevantTransactionProcessed -= h);
await rpc.GenerateAsync(10);
await WaitForFiltersToBeProcessedAsync(TimeSpan.FromSeconds(120), 10);
- doubleSpend = await doubleSpendAwaiter.WaitAsync(TimeSpan.FromSeconds(21));
- Assert.Equal(srtxwwres.SignedTransaction.GetHash(), doubleSpend.SmartTransaction.GetHash());
- Assert.NotEmpty(doubleSpend.Coins);
+ var walletRelevantTransactionProcessed = await walletRelevantTransactionProcessedAwaiter.WaitAsync(TimeSpan.FromSeconds(21));
+ var doubleSpend = Assert.Single(walletRelevantTransactionProcessed.SuccessfullyDoubleSpentCoins);
+ Assert.Equal(srtxwwres.SignedTransaction.GetHash(), doubleSpend.TransactionId);
var curBlockHash = await rpc.GetBestBlockHashAsync();
blockCount = await rpc.GetBlockCountAsync();

MODIFY
WalletWasabi.Tests/UnitTests/Transactions/TransactionProcessorTests.cs
WalletWasabi.Tests/UnitTests/Transactions/TransactionProcessorTests.cs
@@ -27,7 +27,7 @@ namespace WalletWasabi.Tests.UnitTests.Transactions
var relevant = transactionProcessor.Process(tx);
- Assert.False(relevant);
+ Assert.False(relevant.IsWalletRelevant);
Assert.Empty(transactionProcessor.Coins);
Assert.True(transactionProcessor.TransactionStore.MempoolStore.IsEmpty());
Assert.True(transactionProcessor.TransactionStore.ConfirmedStore.IsEmpty());
@@ -43,7 +43,7 @@ namespace WalletWasabi.Tests.UnitTests.Transactions
var tx = CreateCreditingTransaction(keys.First().P2pkhScript, Money.Coins(1.0m));
var relevant = transactionProcessor.Process(tx);
- Assert.False(relevant);
+ Assert.False(relevant.IsWalletRelevant);
Assert.Empty(transactionProcessor.Coins);
Assert.True(transactionProcessor.TransactionStore.MempoolStore.IsEmpty());
Assert.True(transactionProcessor.TransactionStore.ConfirmedStore.IsEmpty());
@@ -59,7 +59,7 @@ namespace WalletWasabi.Tests.UnitTests.Transactions
var relevant = transactionProcessor.Process(tx);
- Assert.False(relevant);
+ Assert.False(relevant.IsWalletRelevant);
Assert.Empty(transactionProcessor.Coins);
Assert.True(transactionProcessor.TransactionStore.MempoolStore.IsEmpty());
Assert.True(transactionProcessor.TransactionStore.ConfirmedStore.IsEmpty());
@@ -75,7 +75,7 @@ namespace WalletWasabi.Tests.UnitTests.Transactions
var relevant = transactionProcessor.Process(tx);
- Assert.False(relevant);
+ Assert.False(relevant.IsWalletRelevant);
Assert.Empty(transactionProcessor.Coins);
Assert.True(transactionProcessor.TransactionStore.MempoolStore.IsEmpty());
Assert.True(transactionProcessor.TransactionStore.ConfirmedStore.IsEmpty());
@@ -103,7 +103,7 @@ namespace WalletWasabi.Tests.UnitTests.Transactions
tx = new SmartTransaction(tx.Transaction, blockHeight);
var relevant = transactionProcessor.Process(tx);
- Assert.True(relevant);
+ Assert.True(relevant.IsWalletRelevant);
Assert.Single(transactionProcessor.Coins);
// Transaction store assertions
@@ -135,7 +135,7 @@ namespace WalletWasabi.Tests.UnitTests.Transactions
var tx2 = CreateSpendingTransaction(createdCoin, keys[2].PubKey.WitHash.ScriptPubKey);
var relevant = transactionProcessor.Process(tx2);
- Assert.False(relevant);
+ Assert.False(relevant.IsWalletRelevant);
Assert.Single(transactionProcessor.Coins, coin => coin.Unspent);
Assert.Single(transactionProcessor.Coins.AsAllCoinsView(), coin => !coin.Unspent);
@@ -154,14 +154,18 @@ namespace WalletWasabi.Tests.UnitTests.Transactions
var keys = transactionProcessor.KeyManager.GetKeys().ToArray();
- int doubleSpendReceivedCalled = 0;
- transactionProcessor.DoubleSpendReceived += (s, e) =>
+ int doubleSpendReceived = 0;
+ transactionProcessor.WalletRelevantTransactionProcessed += (s, e) =>
{
- var coin = Assert.Single(e.Coins);
+ var doubleSpents = e.SuccessfullyDoubleSpentCoins;
+ if (doubleSpents.Any())
+ {
+ var coin = Assert.Single(doubleSpents);
// Double spend to ourselves but to a different address. So checking the address.
Assert.Equal(keys[1].PubKey.WitHash.ScriptPubKey, coin.ScriptPubKey);
- doubleSpendReceivedCalled++;
+ doubleSpendReceived++;
+ }
};
int coinReceivedCalled = 0;
@@ -192,13 +196,13 @@ namespace WalletWasabi.Tests.UnitTests.Transactions
var tx1 = CreateSpendingTransaction(createdCoin, keys[1].PubKey.WitHash.ScriptPubKey);
transactionProcessor.Process(tx1);
- Assert.Equal(0, doubleSpendReceivedCalled);
+ Assert.Equal(0, doubleSpendReceived);
// Spend it coin
var tx2 = CreateSpendingTransaction(createdCoin, keys[2].PubKey.WitHash.ScriptPubKey, height: 54321);
var relevant = transactionProcessor.Process(tx2);
- Assert.Equal(1, doubleSpendReceivedCalled);
+ Assert.Equal(1, doubleSpendReceived);
- Assert.True(relevant);
+ Assert.True(relevant.IsWalletRelevant);
Assert.Single(transactionProcessor.Coins, coin => coin.Unspent && coin.Confirmed);
Assert.Single(transactionProcessor.Coins.AsAllCoinsView(), coin => !coin.Unspent && coin.Confirmed);
@@ -250,7 +254,7 @@ namespace WalletWasabi.Tests.UnitTests.Transactions
tx1.Transaction.Outputs[0].Value = Money.Coins(0.95m);
tx1.Transaction.Outputs.Add(Money.Coins(0.1m), transactionProcessor.NewKey("C").P2wpkhScript);
var relevant = transactionProcessor.Process(tx1);
- Assert.True(relevant);
+ Assert.True(relevant.IsWalletRelevant);
Assert.Equal(0, replaceTransactionReceivedCalled);
var unconfirmedCoin1 = Assert.Single(transactionProcessor.Coins, coin => coin.HdPubKey.Label == "B");
@@ -262,7 +266,7 @@ namespace WalletWasabi.Tests.UnitTests.Transactions
var tx2 = CreateSpendingTransaction(unconfirmedCoin1.GetCoin(), transactionProcessor.NewKey("D").P2wpkhScript);
tx2.Transaction.Outputs[0].Value = Money.Coins(0.7m);
relevant = transactionProcessor.Process(tx2);
- Assert.True(relevant);
+ Assert.True(relevant.IsWalletRelevant);
Assert.Equal(0, replaceTransactionReceivedCalled);
// Spend it coin
@@ -270,7 +274,7 @@ namespace WalletWasabi.Tests.UnitTests.Transactions
tx3.Transaction.Outputs[0].Value = Money.Coins(0.9m);
relevant = transactionProcessor.Process(tx3);
- Assert.True(relevant);
+ Assert.True(relevant.IsWalletRelevant);
Assert.Equal(1, replaceTransactionReceivedCalled);
var finalCoin = Assert.Single(transactionProcessor.Coins);
Assert.True(finalCoin.IsReplaceable);
@@ -359,7 +363,7 @@ namespace WalletWasabi.Tests.UnitTests.Transactions
var tx2 = CreateSpendingTransaction(createdCoins, destinationScript, changeScript); // spends 1.2btc
tx2.Transaction.Inputs[0].Sequence = Sequence.OptInRBF;
var relevant = transactionProcessor.Process(tx2);
- Assert.True(relevant);
+ Assert.True(relevant.IsWalletRelevant);
// Another confirmed segwit transaction for us
var tx3 = CreateCreditingTransaction(NewScript("C"), Money.Coins(1.0m), height: 54322);
@@ -374,7 +378,7 @@ namespace WalletWasabi.Tests.UnitTests.Transactions
var tx4 = CreateSpendingTransaction(coinsToSpend, destinationScript, changeScript);
relevant = transactionProcessor.Process(tx4);
- Assert.True(relevant);
+ Assert.True(relevant.IsWalletRelevant);
var coin = Assert.Single(transactionProcessor.Coins);
Assert.True(coin.IsReplaceable);
@@ -420,14 +424,14 @@ namespace WalletWasabi.Tests.UnitTests.Transactions
var tx2 = CreateSpendingTransaction(createdCoins, destinationScript, changeScript); // spends 1.2btc
tx2.Transaction.Inputs[0].Sequence = Sequence.OptInRBF;
var relevant = transactionProcessor.Process(tx2);
- Assert.True(relevant);
+ Assert.True(relevant.IsWalletRelevant);
// replace previous tx with another one spending only one coin
destinationScript = new Key().PubKey.WitHash.ScriptPubKey; // spend to someone else
var tx3 = CreateSpendingTransaction(createdCoins.Take(1), destinationScript, changeScript); // spends 0.6btc
relevant = transactionProcessor.Process(tx3);
- Assert.True(relevant);
+ Assert.True(relevant.IsWalletRelevant);
var replaceableCoin = Assert.Single(transactionProcessor.Coins, c => c.IsReplaceable);
Assert.Equal(tx3.Transaction.GetHash(), replaceableCoin.TransactionId);
@@ -457,7 +461,7 @@ namespace WalletWasabi.Tests.UnitTests.Transactions
var relevant = transactionProcessor.Process(tx);
// It is relevant because is funding the wallet
- Assert.True(relevant);
+ Assert.True(relevant.IsWalletRelevant);
var coin = Assert.Single(transactionProcessor.Coins);
Assert.Equal(Money.Coins(1.0m), coin.Amount);
Assert.NotNull(receivedCoin);
@@ -486,7 +490,7 @@ namespace WalletWasabi.Tests.UnitTests.Transactions
var tx1 = CreateSpendingTransaction(createdCoin, new Key().PubKey.ScriptPubKey);
var relevant = transactionProcessor.Process(tx1);
- Assert.True(relevant);
+ Assert.True(relevant.IsWalletRelevant);
var coin = Assert.Single(transactionProcessor.Coins.AsAllCoinsView());
Assert.False(coin.Unspent);
Assert.NotNull(spentCoin);
@@ -512,7 +516,7 @@ namespace WalletWasabi.Tests.UnitTests.Transactions
var relevant = transactionProcessor.Process(tx);
// It is relevant even when all the coins can be dust.
- Assert.True(relevant);
+ Assert.True(relevant.IsWalletRelevant);
Assert.Empty(transactionProcessor.Coins);
// Transaction store assertions
@@ -542,7 +546,7 @@ namespace WalletWasabi.Tests.UnitTests.Transactions
var relevant = transactionProcessor.Process(new SmartTransaction(tx, Height.Mempool));
// It is relevant even when all the coins can be dust.
- Assert.True(relevant);
+ Assert.True(relevant.IsWalletRelevant);
var coin = Assert.Single(transactionProcessor.Coins);
Assert.Equal(4, coin.AnonymitySet);
Assert.Equal(amount, coin.Amount);
@@ -573,7 +577,7 @@ namespace WalletWasabi.Tests.UnitTests.Transactions
var relevant = transactionProcessor.Process(new SmartTransaction(tx, Height.Mempool));
// It is relevant even when all the coins can be dust.
- Assert.True(relevant);
+ Assert.True(relevant.IsWalletRelevant);
var coin = Assert.Single(transactionProcessor.Coins, c => c.AnonymitySet > 1);
Assert.Equal(5, coin.AnonymitySet);
Assert.Equal(amount, coin.Amount);

ADD
WalletWasabi/Blockchain/TransactionProcessing/TransactionProcessedResult.cs
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using WalletWasabi.Blockchain.TransactionOutputs;
+using WalletWasabi.Blockchain.Transactions;
+using WalletWasabi.Helpers;
+
+namespace WalletWasabi.Blockchain.TransactionProcessing
+{
+ public class TransactionProcessedResult
+ {
+ public SmartTransaction Transaction { get; }
+ public bool IsWalletRelevant => IsLikelyOwnCoinJoin || SuccessfullyDoubleSpentCoins.Any();
+ public bool IsLikelyOwnCoinJoin { get; set; } = false;
+ public List<SmartCoin> SuccessfullyDoubleSpentCoins { get; set; } = new List<SmartCoin>();
+
+ public TransactionProcessedResult(SmartTransaction transaction)
+ {
+ Transaction = Guard.NotNull(nameof(transaction), transaction);
+ }
+ }
+}

MODIFY
WalletWasabi/Blockchain/TransactionProcessing/TransactionProcessor.cs
WalletWasabi/Blockchain/TransactionProcessing/TransactionProcessor.cs
@@ -20,17 +20,14 @@ namespace WalletWasabi.Blockchain.TransactionProcessing
public CoinsRegistry Coins { get; }
public Money DustThreshold { get; }
+ public event EventHandler<TransactionProcessedResult> WalletRelevantTransactionProcessed;
+
public event EventHandler<TxCoinsEventArgs> CoinsSpent;
public event EventHandler<TxCoinsEventArgs> SpendersConfirmed;
public event EventHandler<TxCoinsEventArgs> CoinsReceived;
- /// <summary>
- /// Received a confirmed double spend transaction.
- /// </summary>
- public event EventHandler<TxCoinsEventArgs> DoubleSpendReceived;
-
public event EventHandler<ReplaceTransactionReceivedEventArgs> ReplaceTransactionReceived;
public TransactionProcessor(
@@ -45,30 +42,42 @@ namespace WalletWasabi.Blockchain.TransactionProcessing
Coins = new CoinsRegistry(privacyLevelThreshold);
}
- public IEnumerable<bool> Process(IEnumerable<SmartTransaction> txs)
+ public IEnumerable<TransactionProcessedResult> Process(IEnumerable<SmartTransaction> txs)
{
+ var rets = new List<TransactionProcessedResult>();
+
lock (Lock)
{
- var ret = new List<bool>();
foreach (var tx in txs)
{
- ret.Add(ProcessNoLock(tx));
+ rets.Add(ProcessNoLock(tx));
}
- return ret;
}
+
+ foreach (var ret in rets.Where(x => x.IsWalletRelevant))
+ {
+ WalletRelevantTransactionProcessed?.Invoke(this, ret);
+ }
+
+ return rets;
}
- public bool Process(SmartTransaction tx)
+ public TransactionProcessedResult Process(SmartTransaction tx)
{
+ TransactionProcessedResult ret;
lock (Lock)
{
- return ProcessNoLock(tx);
+ ret = ProcessNoLock(tx);
}
+ WalletRelevantTransactionProcessed?.Invoke(this, ret);
+ return ret;
}
- private bool ProcessNoLock(SmartTransaction tx)
+ private TransactionProcessedResult ProcessNoLock(SmartTransaction tx)
{
var walletRelevant = false;
+ var result = new TransactionProcessedResult(tx);
+
// We do not care about non-witness transactions for other than mempool cleanup.
if (tx.Transaction.PossiblyP2WPKHInvolved())
{
@@ -136,8 +145,7 @@ namespace WalletWasabi.Blockchain.TransactionProcessing
}
else
{
- DoubleSpendReceived?.Invoke(this, new TxCoinsEventArgs(tx, Enumerable.Empty<SmartCoin>()));
- return false;
+ return result;
}
}
else // new confirmation always enjoys priority
@@ -148,7 +156,7 @@ namespace WalletWasabi.Blockchain.TransactionProcessing
Coins.Remove(doubleSpentCoin);
}
- DoubleSpendReceived?.Invoke(this, new TxCoinsEventArgs(tx, doubleSpends));
+ result.SuccessfullyDoubleSpentCoins.AddRange(doubleSpends);
var unconfirmedDoubleSpentTxId = doubleSpends.First().TransactionId;
TransactionStore.MempoolStore.TryRemove(unconfirmedDoubleSpentTxId, out _);
@@ -157,7 +165,6 @@ namespace WalletWasabi.Blockchain.TransactionProcessing
}
}
- var isLikelyCoinJoinOutput = false;
bool hasEqualOutputs = tx.Transaction.GetIndistinguishableOutputs(includeSingle: false).FirstOrDefault() != default;
if (hasEqualOutputs)
{
@@ -172,7 +179,7 @@ namespace WalletWasabi.Blockchain.TransactionProcessing
if (receivedAlmostAsMuchAsSpent)
{
- isLikelyCoinJoinOutput = true;
+ result.IsLikelyOwnCoinJoin = true;
}
}
}
@@ -203,7 +210,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, isLikelyCoinJoinOutput, 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, result.IsLikelyOwnCoinJoin, foundKey.Label, spenderTransactionId: null, false, pubKey: foundKey); // Do not inherit locked status from key, that's different.
// If we did not have it.
if (Coins.TryAdd(newCoin))
{
@@ -276,7 +283,7 @@ namespace WalletWasabi.Blockchain.TransactionProcessing
}
}
- return walletRelevant;
+ return result;
}
public void UndoBlock(Height blockHeight)

Replace doublespend event with txinfo event