Use merged events
MODIFY
WalletWasabi.Gui/Controls/WalletExplorer/CoinListViewModel.cs
WalletWasabi.Gui/Controls/WalletExplorer/CoinListViewModel.cs
@@ -443,9 +443,9 @@ namespace WalletWasabi.Gui.Controls.WalletExplorer
Observable
.Merge(Observable.FromEventPattern<ReplaceTransactionReceivedEventArgs>(Global.WalletService.TransactionProcessor, nameof(Global.WalletService.TransactionProcessor.ReplaceTransactionReceived)).Select(_ => Unit.Default))
- .Merge(Observable.FromEventPattern<DoubleSpendReceivedEventArgs>(Global.WalletService.TransactionProcessor, nameof(Global.WalletService.TransactionProcessor.DoubleSpendReceived)).Select(_ => Unit.Default))
- .Merge(Observable.FromEventPattern<SmartCoin>(Global.WalletService.TransactionProcessor, nameof(Global.WalletService.TransactionProcessor.CoinSpent)).Select(_ => Unit.Default))
- .Merge(Observable.FromEventPattern<SmartCoin>(Global.WalletService.TransactionProcessor, nameof(Global.WalletService.TransactionProcessor.CoinReceived)).Select(_ => Unit.Default))
+ .Merge(Observable.FromEventPattern<TxCoinsEventArgs>(Global.WalletService.TransactionProcessor, nameof(Global.WalletService.TransactionProcessor.DoubleSpendReceived)).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.
.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,9 +54,9 @@ 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.CoinSpent)))
- .Merge(Observable.FromEventPattern(Global.WalletService.TransactionProcessor, nameof(Global.WalletService.TransactionProcessor.SpenderConfirmed)))
- .Merge(Observable.FromEventPattern(Global.WalletService.TransactionProcessor, nameof(Global.WalletService.TransactionProcessor.CoinReceived)))
+ .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.CoinsReceived)))
.Throttle(TimeSpan.FromSeconds(5))
.ObserveOn(RxApp.MainThreadScheduler)
.Subscribe(async _ => await TryRewriteTableAsync())

MODIFY
WalletWasabi.Gui/Controls/WalletExplorer/ReceiveTabViewModel.cs
WalletWasabi.Gui/Controls/WalletExplorer/ReceiveTabViewModel.cs
@@ -157,7 +157,7 @@ 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.TransactionProcessor, nameof(Global.WalletService.TransactionProcessor.CoinReceived))
+ .FromEventPattern(Global.WalletService.TransactionProcessor, nameof(Global.WalletService.TransactionProcessor.CoinsReceived))
.ObserveOn(RxApp.MainThreadScheduler)
.Subscribe(_ => InitializeAddresses())
.DisposeWith(Disposables);

MODIFY
WalletWasabi.Gui/Controls/WalletExplorer/WalletViewModel.cs
WalletWasabi.Gui/Controls/WalletExplorer/WalletViewModel.cs
@@ -93,8 +93,8 @@ 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.CoinReceived)).Select(_ => Unit.Default),
- Observable.FromEventPattern(Global.WalletService.TransactionProcessor, nameof(Global.WalletService.TransactionProcessor.CoinSpent)).Select(_ => Unit.Default))
+ Observable.FromEventPattern(Global.WalletService.TransactionProcessor, nameof(Global.WalletService.TransactionProcessor.CoinsReceived)).Select(_ => Unit.Default),
+ Observable.FromEventPattern(Global.WalletService.TransactionProcessor, nameof(Global.WalletService.TransactionProcessor.CoinsSpent)).Select(_ => Unit.Default))
.Throttle(TimeSpan.FromSeconds(0.5))
.Merge(Global.UiConfig.WhenAnyValue(x => x.LurkingWifeMode).Select(_ => Unit.Default))
.ObserveOn(RxApp.MainThreadScheduler)

MODIFY
WalletWasabi.Gui/Global.cs
WalletWasabi.Gui/Global.cs
@@ -21,6 +21,7 @@ using WalletWasabi.Blockchain.Analysis.FeesEstimation;
using WalletWasabi.Blockchain.Keys;
using WalletWasabi.Blockchain.TransactionBroadcasting;
using WalletWasabi.Blockchain.TransactionOutputs;
+using WalletWasabi.Blockchain.TransactionProcessing;
using WalletWasabi.CoinJoin.Client;
using WalletWasabi.CoinJoin.Client.Clients;
using WalletWasabi.Gui.Helpers;
@@ -474,7 +475,7 @@ namespace WalletWasabi.Gui
Logger.LogInfo($"{nameof(WalletService)} started.");
token.ThrowIfCancellationRequested();
- WalletService.TransactionProcessor.CoinReceived += CoinReceived;
+ WalletService.TransactionProcessor.CoinsReceived += CoinsReceived;
TransactionBroadcaster.AddWalletService(WalletService);
CoinJoinProcessor.AddWalletService(WalletService);
@@ -544,19 +545,20 @@ namespace WalletWasabi.Gui
return keyManager;
}
- private void CoinReceived(object sender, SmartCoin coin)
+ private void CoinsReceived(object sender, TxCoinsEventArgs args)
{
- if (coin.HdPubKey.IsInternal)
+ if (UiConfig?.LurkingWifeMode is true)
{
return;
}
- if (UiConfig?.LurkingWifeMode is true)
+ Money satSum = args.Coins.Where(x => !x.HdPubKey.IsInternal).Sum(x => x.Amount);
+ if (satSum == Money.Zero)
{
return;
}
- string amountString = coin.Amount.ToString(false, true);
+ string amountString = satSum.ToString(false, true);
NotificationHelpers.Information($"{amountString} BTC", "Received");
}
@@ -566,7 +568,7 @@ namespace WalletWasabi.Gui
var walletService = WalletService;
if (walletService is { })
{
- walletService.TransactionProcessor.CoinReceived -= CoinReceived;
+ walletService.TransactionProcessor.CoinsReceived -= CoinsReceived;
}
try

MODIFY
WalletWasabi.Tests/IntegrationTests/RegTests.cs
WalletWasabi.Tests/IntegrationTests/RegTests.cs
@@ -1475,22 +1475,22 @@ namespace WalletWasabi.Tests.IntegrationTests
srtxwwreq.Transaction = overwriteTx;
var srtxwwres = await rpc.SignRawTransactionWithWalletAsync(srtxwwreq);
- var doubleSpendAwaiter = new EventAwaiter<DoubleSpendReceivedEventArgs>(
+ 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.Remove);
+ Assert.Empty(doubleSpend.Coins);
- doubleSpendAwaiter = new EventAwaiter<DoubleSpendReceivedEventArgs>(
+ doubleSpendAwaiter = new EventAwaiter<TxCoinsEventArgs>(
h => wallet.TransactionProcessor.DoubleSpendReceived += h,
h => wallet.TransactionProcessor.DoubleSpendReceived -= 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.Remove);
+ Assert.NotEmpty(doubleSpend.Coins);
var curBlockHash = await rpc.GetBestBlockHashAsync();
blockCount = await rpc.GetBlockCountAsync();

MODIFY
WalletWasabi.Tests/UnitTests/Transactions/TransactionProcessorTests.cs
WalletWasabi.Tests/UnitTests/Transactions/TransactionProcessorTests.cs
@@ -157,7 +157,7 @@ namespace WalletWasabi.Tests.UnitTests.Transactions
int doubleSpendReceivedCalled = 0;
transactionProcessor.DoubleSpendReceived += (s, e) =>
{
- var coin = Assert.Single(e.Remove);
+ var coin = Assert.Single(e.Coins);
// Double spend to ourselves but to a different address. So checking the address.
Assert.Equal(keys[1].PubKey.WitHash.ScriptPubKey, coin.ScriptPubKey);

MODIFY
WalletWasabi/Blockchain/TransactionProcessing/TransactionProcessor.cs
WalletWasabi/Blockchain/TransactionProcessing/TransactionProcessor.cs
@@ -20,16 +20,16 @@ namespace WalletWasabi.Blockchain.TransactionProcessing
public CoinsRegistry Coins { get; }
public Money DustThreshold { get; }
- public event EventHandler<SmartCoin> CoinSpent;
+ public event EventHandler<TxCoinsEventArgs> CoinsSpent;
- public event EventHandler<SmartCoin> SpenderConfirmed;
+ public event EventHandler<TxCoinsEventArgs> SpendersConfirmed;
- public event EventHandler<SmartCoin> CoinReceived;
+ public event EventHandler<TxCoinsEventArgs> CoinsReceived;
/// <summary>
/// Received a confirmed double spend transaction.
/// </summary>
- public event EventHandler<DoubleSpendReceivedEventArgs> DoubleSpendReceived;
+ public event EventHandler<TxCoinsEventArgs> DoubleSpendReceived;
public event EventHandler<ReplaceTransactionReceivedEventArgs> ReplaceTransactionReceived;
@@ -136,7 +136,7 @@ namespace WalletWasabi.Blockchain.TransactionProcessing
}
else
{
- DoubleSpendReceived?.Invoke(this, new DoubleSpendReceivedEventArgs(tx, Enumerable.Empty<SmartCoin>()));
+ DoubleSpendReceived?.Invoke(this, new TxCoinsEventArgs(tx, Enumerable.Empty<SmartCoin>()));
return false;
}
}
@@ -148,7 +148,7 @@ namespace WalletWasabi.Blockchain.TransactionProcessing
Coins.Remove(doubleSpentCoin);
}
- DoubleSpendReceived?.Invoke(this, new DoubleSpendReceivedEventArgs(tx, doubleSpends));
+ DoubleSpendReceived?.Invoke(this, new TxCoinsEventArgs(tx, doubleSpends));
var unconfirmedDoubleSpentTxId = doubleSpends.First().TransactionId;
TransactionStore.MempoolStore.TryRemove(unconfirmedDoubleSpentTxId, out _);
@@ -178,6 +178,8 @@ namespace WalletWasabi.Blockchain.TransactionProcessing
}
List<SmartCoin> newCoins = new List<SmartCoin>();
+ 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++)
{
@@ -245,12 +247,12 @@ namespace WalletWasabi.Blockchain.TransactionProcessing
if (!alreadyKnown)
{
Coins.Spend(foundCoin);
- CoinSpent?.Invoke(this, foundCoin);
+ coinsSpent.Add(foundCoin);
}
if (tx.Confirmed)
{
- SpenderConfirmed?.Invoke(this, foundCoin);
+ spendersConfirmed.Add(foundCoin);
}
}
}
@@ -260,9 +262,17 @@ namespace WalletWasabi.Blockchain.TransactionProcessing
TransactionStore.AddOrUpdate(tx);
}
- foreach (var newCoin in newCoins)
+ if (coinsSpent.Any())
{
- CoinReceived?.Invoke(this, newCoin);
+ CoinsSpent?.Invoke(this, new TxCoinsEventArgs(tx, coinsSpent));
+ }
+ if (spendersConfirmed.Any())
+ {
+ SpendersConfirmed?.Invoke(this, new TxCoinsEventArgs(tx, spendersConfirmed));
+ }
+ if (newCoins.Any())
+ {
+ CoinsReceived?.Invoke(this, new TxCoinsEventArgs(tx, newCoins));
}
}

RENAME
WalletWasabi/Blockchain/TransactionProcessing/DoubleSpendReceivedEventArgs.cs
WalletWasabi/Blockchain/TransactionProcessing/TxCoinsEventArgs.cs
using System;
using System.Collections.Generic;
+using System.Text;
using WalletWasabi.Blockchain.TransactionOutputs;
using WalletWasabi.Blockchain.Transactions;
namespace WalletWasabi.Blockchain.TransactionProcessing
{
- public class DoubleSpendReceivedEventArgs : EventArgs
+ public class TxCoinsEventArgs : EventArgs
{
- public DoubleSpendReceivedEventArgs(SmartTransaction smartTransaction, IEnumerable<SmartCoin> remove) : base()
+ public TxCoinsEventArgs(SmartTransaction smartTransaction, IEnumerable<SmartCoin> coins) : base()
{
SmartTransaction = smartTransaction;
- Remove = remove;
+ Coins = coins;
}
public SmartTransaction SmartTransaction { get; }
- public IEnumerable<SmartCoin> Remove { get; }
+ public IEnumerable<SmartCoin> Coins { get; }
}
}

MODIFY
WalletWasabi/CoinJoin/Client/Clients/CoinJoinClient.cs
WalletWasabi/CoinJoin/Client/Clients/CoinJoinClient.cs
@@ -737,7 +737,7 @@ namespace WalletWasabi.CoinJoin.Client.Clients
return (change, actives);
}
- public async Task QueueCoinsToMixAsync(params SmartCoin[] coins)
+ public async Task QueueCoinsToMixAsync(IEnumerable<SmartCoin> coins)
{
await QueueCoinsToMixAsync(SaltSoup(), coins).ConfigureAwait(false);
}
@@ -756,7 +756,7 @@ namespace WalletWasabi.CoinJoin.Client.Clients
private string Soup { get; set; } = null;
private object RefrigeratorLock { get; } = new object();
- public async Task<IEnumerable<SmartCoin>> QueueCoinsToMixAsync(string password, params SmartCoin[] coins)
+ public async Task<IEnumerable<SmartCoin>> QueueCoinsToMixAsync(string password, IEnumerable<SmartCoin> coins)
{
if (coins is null || !coins.Any() || IsQuitPending)
{

MODIFY
WalletWasabi/CoinJoin/Client/Rounds/ClientState.cs
WalletWasabi/CoinJoin/Client/Rounds/ClientState.cs
@@ -68,7 +68,7 @@ namespace WalletWasabi.CoinJoin.Client.Rounds
}
}
- public bool Contains(params TxoRef[] txos)
+ public bool Contains(IEnumerable<TxoRef> txos)
{
lock (StateLock)
{

MODIFY
WalletWasabi/Services/WalletService.cs
WalletWasabi/Services/WalletService.cs
@@ -115,8 +115,8 @@ namespace WalletWasabi.Services
TransactionProcessor = new TransactionProcessor(BitcoinStore.TransactionStore, KeyManager, ServiceConfiguration.DustThreshold, ServiceConfiguration.PrivacyLevelStrong);
Coins = TransactionProcessor.Coins;
- TransactionProcessor.CoinSpent += TransactionProcessor_CoinSpent;
- TransactionProcessor.CoinReceived += TransactionProcessor_CoinReceivedAsync;
+ TransactionProcessor.CoinsSpent += TransactionProcessor_CoinsSpent;
+ TransactionProcessor.CoinsReceived += TransactionProcessor_CoinsReceivedAsync;
if (Directory.Exists(BlocksFolderPath))
{
@@ -142,11 +142,14 @@ namespace WalletWasabi.Services
BitcoinStore.MempoolService.TransactionReceived += Mempool_TransactionReceived;
}
- private void TransactionProcessor_CoinSpent(object sender, SmartCoin spentCoin)
+ private void TransactionProcessor_CoinsSpent(object sender, TxCoinsEventArgs args)
{
try
{
- ChaumianClient.ExposedLinks.TryRemove(spentCoin.GetTxoRef(), out _);
+ foreach (var coin in args.Coins)
+ {
+ ChaumianClient.ExposedLinks.TryRemove(coin.GetTxoRef(), out _);
+ }
}
catch (Exception ex)
{
@@ -154,16 +157,24 @@ namespace WalletWasabi.Services
}
}
- private async void TransactionProcessor_CoinReceivedAsync(object sender, SmartCoin newCoin)
+ private async void TransactionProcessor_CoinsReceivedAsync(object sender, TxCoinsEventArgs args)
{
try
+ {
+ if (ChaumianClient.State.Contains(args.SmartTransaction.Transaction.Inputs.Select(x => x.PrevOut.ToTxoRef())))
+ {
+ var coinsToQueue = new List<SmartCoin>();
+ foreach (var newCoin in args.Coins)
{
// If it's being mixed and anonset is not sufficient, then queue it.
if (newCoin.Unspent && ChaumianClient.HasIngredients
- && newCoin.AnonymitySet < ServiceConfiguration.MixUntilAnonymitySet
- && ChaumianClient.State.Contains(newCoin.SpentOutputs))
+ && newCoin.AnonymitySet < ServiceConfiguration.MixUntilAnonymitySet)
{
- await ChaumianClient.QueueCoinsToMixAsync(newCoin);
+ coinsToQueue.Add(newCoin);
+ }
+ }
+
+ await ChaumianClient.QueueCoinsToMixAsync(coinsToQueue).ConfigureAwait(false);
}
}
catch (Exception ex)
@@ -811,8 +822,8 @@ namespace WalletWasabi.Services
BitcoinStore.IndexStore.NewFilter -= IndexDownloader_NewFilterAsync;
BitcoinStore.IndexStore.Reorged -= IndexDownloader_ReorgedAsync;
BitcoinStore.MempoolService.TransactionReceived -= Mempool_TransactionReceived;
- TransactionProcessor.CoinSpent -= TransactionProcessor_CoinSpent;
- TransactionProcessor.CoinReceived -= TransactionProcessor_CoinReceivedAsync;
+ TransactionProcessor.CoinsSpent -= TransactionProcessor_CoinsSpent;
+ TransactionProcessor.CoinsReceived -= TransactionProcessor_CoinsReceivedAsync;
DisconnectDisposeNullLocalBitcoinCoreNode();
}

Use merged events