Implement rest of the events.
MODIFY
WalletWasabi.Gui/Controls/WalletExplorer/CoinListViewModel.cs
WalletWasabi.Gui/Controls/WalletExplorer/CoinListViewModel.cs
@@ -443,7 +443,6 @@ namespace WalletWasabi.Gui.Controls.WalletExplorer
Observable
.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))
.Throttle(TimeSpan.FromSeconds(2)) // Throttle TransactionProcessor events adds/removes.
.Merge(Observable.FromEventPattern(this, nameof(CoinListShown), RxApp.MainThreadScheduler).Select(_ => Unit.Default)) // Load the list immediately.
.ObserveOn(RxApp.MainThreadScheduler)

MODIFY
WalletWasabi.Gui/Controls/WalletExplorer/HistoryTabViewModel.cs
WalletWasabi.Gui/Controls/WalletExplorer/HistoryTabViewModel.cs
@@ -54,8 +54,6 @@ namespace WalletWasabi.Gui.Controls.WalletExplorer
Disposables = Disposables is null ? new CompositeDisposable() : throw new NotSupportedException($"Cannot open {GetType().Name} before closing it.");
Observable.FromEventPattern(Global.WalletService, nameof(Global.WalletService.NewBlockProcessed))
- .Merge(Observable.FromEventPattern(Global.WalletService.TransactionProcessor, nameof(Global.WalletService.TransactionProcessor.CoinsSpent)))
- .Merge(Observable.FromEventPattern(Global.WalletService.TransactionProcessor, nameof(Global.WalletService.TransactionProcessor.SpendersConfirmed)))
.Merge(Observable.FromEventPattern(Global.WalletService.TransactionProcessor, nameof(Global.WalletService.TransactionProcessor.WalletRelevantTransactionProcessed)))
.Throttle(TimeSpan.FromSeconds(3))
.ObserveOn(RxApp.MainThreadScheduler)

MODIFY
WalletWasabi.Gui/Controls/WalletExplorer/WalletViewModel.cs
WalletWasabi.Gui/Controls/WalletExplorer/WalletViewModel.cs
@@ -93,8 +93,7 @@ namespace WalletWasabi.Gui.Controls.WalletExplorer
Disposables = Disposables is null ? new CompositeDisposable() : throw new NotSupportedException($"Cannot open {GetType().Name} before closing it.");
Observable.Merge(
- Observable.FromEventPattern(Global.WalletService.TransactionProcessor, nameof(Global.WalletService.TransactionProcessor.WalletRelevantTransactionProcessed)).Select(_ => Unit.Default),
- Observable.FromEventPattern(Global.WalletService.TransactionProcessor, nameof(Global.WalletService.TransactionProcessor.CoinsSpent)).Select(_ => Unit.Default))
+ Observable.FromEventPattern(Global.WalletService.TransactionProcessor, nameof(Global.WalletService.TransactionProcessor.WalletRelevantTransactionProcessed)).Select(_ => Unit.Default))
.Throttle(TimeSpan.FromSeconds(0.1))
.Merge(Global.UiConfig.WhenAnyValue(x => x.LurkingWifeMode).Select(_ => Unit.Default))
.ObserveOn(RxApp.MainThreadScheduler)

MODIFY
WalletWasabi.Tests/IntegrationTests/RegTests.cs
WalletWasabi.Tests/IntegrationTests/RegTests.cs
@@ -1624,13 +1624,9 @@ namespace WalletWasabi.Tests.IntegrationTests
eventAwaiter = new EventAwaiter<TransactionProcessedResult>(
h => wallet.TransactionProcessor.WalletRelevantTransactionProcessed += h,
h => wallet.TransactionProcessor.WalletRelevantTransactionProcessed -= h);
- var spendWaiter = new EventAwaiter<TxCoinsEventArgs>(
- h => wallet.TransactionProcessor.CoinsSpent += h,
- h => wallet.TransactionProcessor.CoinsSpent -= h);
await broadcaster.SendTransactionAsync(tx1Res.Transaction);
- var spentCoins = await spendWaiter.WaitAsync(TimeSpan.FromSeconds(21));
- Assert.Equal(tx0Id, spentCoins.Coins.Single().TransactionId);
var eventArgs = await eventAwaiter.WaitAsync(TimeSpan.FromSeconds(21));
+ Assert.Equal(tx0Id, eventArgs.NewlySpentCoins.Single().TransactionId);
Assert.Equal(tx1Res.Transaction.GetHash(), eventArgs.NewlyReceivedCoins.First().TransactionId);
// There is a coin created by the latest spending transaction
@@ -1653,14 +1649,12 @@ namespace WalletWasabi.Tests.IntegrationTests
eventAwaiter = new EventAwaiter<TransactionProcessedResult>(
h => wallet.TransactionProcessor.WalletRelevantTransactionProcessed += h,
h => wallet.TransactionProcessor.WalletRelevantTransactionProcessed -= h);
- spendWaiter = new EventAwaiter<TxCoinsEventArgs>(
- h => wallet.TransactionProcessor.CoinsSpent += h,
- h => wallet.TransactionProcessor.CoinsSpent -= h);
await broadcaster.SendTransactionAsync(tx2Res.Transaction);
- spentCoins = await spendWaiter.WaitAsync(TimeSpan.FromSeconds(21));
- Assert.Equal(tx1Res.Transaction.GetHash(), spentCoins.Coins.First().TransactionId);
- var receivedCoins = (await eventAwaiter.WaitAsync(TimeSpan.FromSeconds(21))).NewlyReceivedCoins.ToArray();
+ eventArgs = await eventAwaiter.WaitAsync(TimeSpan.FromSeconds(21));
+ var spentCoins = eventArg.NewlySpentCoins.ToArray();
+ Assert.Equal(tx1Res.Transaction.GetHash(), spentCoins.First().TransactionId);
uint256 tx2Hash = tx2Res.Transaction.GetHash();
+ var receivedCoins = eventArg.NewlyReceivedCoins.ToArray();
Assert.Equal(tx2Hash, receivedCoins[0].TransactionId);
Assert.Equal(tx2Hash, receivedCoins[1].TransactionId);

MODIFY
WalletWasabi.Tests/UnitTests/Transactions/TransactionProcessorTests.cs
WalletWasabi.Tests/UnitTests/Transactions/TransactionProcessorTests.cs
@@ -307,8 +307,17 @@ namespace WalletWasabi.Tests.UnitTests.Transactions
{
coinsReceived += e.NewlyReceivedCoins.Count;
};
- transactionProcessor.CoinsSpent += (s, e) => throw new InvalidOperationException("We are not spending the coin.");
- transactionProcessor.SpendersConfirmed += (s, e) => confirmed++;
+ transactionProcessor.WalletRelevantTransactionProcessed += (s, e) =>
+ {
+ if (e.NewlySpentCoins.Any())
+ {
+ throw new InvalidOperationException("We are not spending the coin.");
+ }
+ else if (e.NewlyConfirmedSpentCoins.Any())
+ {
+ confirmed++;
+ }
+ };
// A confirmed segwit transaction for us
var tx1 = CreateCreditingTransaction(keys[0].PubKey.WitHash.ScriptPubKey, Money.Coins(1.0m));
@@ -486,7 +495,13 @@ namespace WalletWasabi.Tests.UnitTests.Transactions
{
var transactionProcessor = await CreateTransactionProcessorAsync();
SmartCoin spentCoin = null;
- transactionProcessor.CoinsSpent += (s, theCoin) => spentCoin = theCoin.Coins.Single();
+ transactionProcessor.WalletRelevantTransactionProcessed += (s, e) =>
+ {
+ if (e.NewlySpentCoins.Any())
+ {
+ spentCoin = e.NewlySpentCoins.Single();
+ }
+ };
var keys = transactionProcessor.KeyManager.GetKeys();
var tx0 = CreateCreditingTransaction(keys.First().P2wpkhScript, Money.Coins(1.0m));
transactionProcessor.Process(tx0);
@@ -514,8 +529,14 @@ namespace WalletWasabi.Tests.UnitTests.Transactions
public async Task ReceiveTransactionWithDustForWalletAsync()
{
var transactionProcessor = await CreateTransactionProcessorAsync();
- transactionProcessor.WalletRelevantTransactionProcessed += (s, e)
- => throw new Exception("The dust coin raised an event when it shouldn't.");
+ transactionProcessor.WalletRelevantTransactionProcessed += (s, e) =>
+ {
+ // The dust coin should raise an event, but it shouldn't be fully processed.
+ Assert.NotEmpty(e.ReceivedDusts);
+ Assert.Empty(e.ReceivedCoins);
+ Assert.Empty(e.NewlyReceivedCoins);
+ Assert.Empty(e.NewlyConfirmedReceivedCoins);
+ };
var keys = transactionProcessor.KeyManager.GetKeys();
var tx = CreateCreditingTransaction(keys.First().P2wpkhScript, Money.Coins(0.000099m));

MODIFY
WalletWasabi/Blockchain/TransactionProcessing/TransactionProcessedResult.cs
WalletWasabi/Blockchain/TransactionProcessing/TransactionProcessedResult.cs
+using NBitcoin;
using System;
using System.Collections.Generic;
using System.Linq;
@@ -17,10 +18,19 @@ namespace WalletWasabi.Blockchain.TransactionProcessing
|| ReplacedCoins.Any()
|| RestoredCoins.Any()
|| NewlyReceivedCoins.Any()
- || NewlyConfirmedReceivedCoins.Any();
+ || NewlyConfirmedReceivedCoins.Any()
+ || NewlySpentCoins.Any()
+ || NewlyConfirmedSpentCoins.Any()
+ || ReceivedDusts.Any();
public bool IsLikelyOwnCoinJoin { get; set; } = false;
+ /// <summary>
+ /// Dust outputs we received in this transaction. We may or may not have known about
+ /// them previously. They aren't SmartCoins, because they aren't fully processed.
+ /// </summary>
+ public List<TxOut> ReceivedDusts { get; set; } = new List<TxOut>();
+
/// <summary>
/// Coins we received in this transaction.
/// </summary>
@@ -36,6 +46,21 @@ namespace WalletWasabi.Blockchain.TransactionProcessing
/// </summary>
public List<SmartCoin> NewlyConfirmedReceivedCoins { get; set; } = new List<SmartCoin>();
+ /// <summary>
+ /// Coins we spent in this transaction.
+ /// </summary>
+ public List<SmartCoin> SpentCoins { get; set; } = new List<SmartCoin>();
+
+ /// <summary>
+ /// Coins we spent in this transaction and we did not previously known about.
+ /// </summary>
+ public List<SmartCoin> NewlySpentCoins { get; set; } = new List<SmartCoin>();
+
+ /// <summary>
+ /// Coins we spent in this transaction, we have known already about, but it just got confirmed.
+ /// </summary>
+ public List<SmartCoin> NewlyConfirmedSpentCoins { get; set; } = new List<SmartCoin>();
+
/// <summary>
/// Coins those we previously had in the mempool, but this confirmed
/// transaction has successfully invalidated them, because it spends

MODIFY
WalletWasabi/Blockchain/TransactionProcessing/TransactionProcessor.cs
WalletWasabi/Blockchain/TransactionProcessing/TransactionProcessor.cs
@@ -22,10 +22,6 @@ namespace WalletWasabi.Blockchain.TransactionProcessing
public event EventHandler<TransactionProcessedResult> WalletRelevantTransactionProcessed;
- public event EventHandler<TxCoinsEventArgs> CoinsSpent;
-
- public event EventHandler<TxCoinsEventArgs> SpendersConfirmed;
-
public TransactionProcessor(
AllTransactionStore transactionStore,
KeyManager keyManager,
@@ -184,8 +180,6 @@ namespace WalletWasabi.Blockchain.TransactionProcessing
}
}
- List<SmartCoin> coinsSpent = new List<SmartCoin>();
- List<SmartCoin> spendersConfirmed = new List<SmartCoin>();
List<SmartCoin> spentOwnCoins = null;
for (var i = 0U; i < tx.Transaction.Outputs.Count; i++)
{
@@ -198,6 +192,7 @@ namespace WalletWasabi.Blockchain.TransactionProcessing
if (output.Value <= DustThreshold)
{
+ result.ReceivedDusts.Add(output);
continue;
}
@@ -252,16 +247,17 @@ namespace WalletWasabi.Blockchain.TransactionProcessing
walletRelevant = true;
var alreadyKnown = foundCoin.SpenderTransactionId == txId;
foundCoin.SpenderTransactionId = txId;
+ result.SpentCoins.Add(foundCoin);
if (!alreadyKnown)
{
Coins.Spend(foundCoin);
- coinsSpent.Add(foundCoin);
+ result.NewlySpentCoins.Add(foundCoin);
}
if (tx.Confirmed)
{
- spendersConfirmed.Add(foundCoin);
+ result.NewlyConfirmedSpentCoins.Add(foundCoin);
}
}
}
@@ -270,15 +266,6 @@ namespace WalletWasabi.Blockchain.TransactionProcessing
{
TransactionStore.AddOrUpdate(tx);
}
-
- if (coinsSpent.Any())
- {
- CoinsSpent?.Invoke(this, new TxCoinsEventArgs(tx, coinsSpent));
- }
- if (spendersConfirmed.Any())
- {
- SpendersConfirmed?.Invoke(this, new TxCoinsEventArgs(tx, spendersConfirmed));
- }
}
return result;

MODIFY
WalletWasabi/Services/WalletService.cs
WalletWasabi/Services/WalletService.cs
@@ -115,7 +115,6 @@ namespace WalletWasabi.Services
TransactionProcessor = new TransactionProcessor(BitcoinStore.TransactionStore, KeyManager, ServiceConfiguration.DustThreshold, ServiceConfiguration.PrivacyLevelStrong);
Coins = TransactionProcessor.Coins;
- TransactionProcessor.CoinsSpent += TransactionProcessor_CoinsSpent;
TransactionProcessor.WalletRelevantTransactionProcessed += TransactionProcessor_WalletRelevantTransactionProcessedAsync;
if (Directory.Exists(BlocksFolderPath))
@@ -142,11 +141,11 @@ namespace WalletWasabi.Services
BitcoinStore.MempoolService.TransactionReceived += Mempool_TransactionReceived;
}
- private void TransactionProcessor_CoinsSpent(object sender, TxCoinsEventArgs args)
+ private async void TransactionProcessor_WalletRelevantTransactionProcessedAsync(object sender, TransactionProcessedResult e)
{
try
{
- foreach (var coin in args.Coins)
+ foreach (var coin in e.NewlySpentCoins.Concat(e.ReplacedCoins).Concat(e.SuccessfullyDoubleSpentCoins).Distinct())
{
ChaumianClient.ExposedLinks.TryRemove(coin.GetTxoRef(), out _);
}
@@ -155,16 +154,16 @@ namespace WalletWasabi.Services
{
Logger.LogError(ex);
}
- }
- private async void TransactionProcessor_WalletRelevantTransactionProcessedAsync(object sender, TransactionProcessedResult e)
- {
try
+ {
+ IEnumerable<SmartCoin> newCoins = e.NewlyReceivedCoins.Concat(e.RestoredCoins).Distinct();
+ if (newCoins.Any())
{
if (ChaumianClient.State.Contains(e.Transaction.Transaction.Inputs.Select(x => x.PrevOut.ToTxoRef())))
{
var coinsToQueue = new HashSet<SmartCoin>();
- foreach (var newCoin in e.NewlyReceivedCoins.Concat(e.RestoredCoins).Distinct())
+ foreach (var newCoin in newCoins)
{
// If it's being mixed and anonset is not sufficient, then queue it.
if (newCoin.Unspent && ChaumianClient.HasIngredients
@@ -177,6 +176,7 @@ namespace WalletWasabi.Services
await ChaumianClient.QueueCoinsToMixAsync(coinsToQueue).ConfigureAwait(false);
}
}
+ }
catch (Exception ex)
{
Logger.LogError(ex);
@@ -822,7 +822,6 @@ namespace WalletWasabi.Services
BitcoinStore.IndexStore.NewFilter -= IndexDownloader_NewFilterAsync;
BitcoinStore.IndexStore.Reorged -= IndexDownloader_ReorgedAsync;
BitcoinStore.MempoolService.TransactionReceived -= Mempool_TransactionReceived;
- TransactionProcessor.CoinsSpent -= TransactionProcessor_CoinsSpent;
TransactionProcessor.WalletRelevantTransactionProcessed -= TransactionProcessor_WalletRelevantTransactionProcessedAsync;
DisconnectDisposeNullLocalBitcoinCoreNode();

Implement rest of the events.