Don't lose unconfirmed transactions.
MODIFY
WalletWasabi.Gui/Global.cs
WalletWasabi.Gui/Global.cs
@@ -542,7 +542,10 @@ namespace WalletWasabi.Gui
WalletService.KeyManager?.ToFile(backupWalletFilePath);
Logger.LogInfo($"{nameof(KeyManager)} backup saved to {backupWalletFilePath}.", nameof(Global));
}
- WalletService?.Dispose();
+ if (WalletService != null)
+ {
+ await WalletService.StopAsync();
+ }
WalletService = null;
Logger.LogInfo($"{nameof(WalletService)} is stopped.", nameof(Global));
}

MODIFY
WalletWasabi.Tests/P2pTests.cs
WalletWasabi.Tests/P2pTests.cs
@@ -146,7 +146,10 @@ namespace WalletWasabi.Tests
{
await walletService?.DeleteBlockAsync(hash);
}
- walletService?.Dispose();
+ if (walletService != null)
+ {
+ await walletService.StopAsync();
+ }
if (Directory.Exists(blocksFolderPath))
{

MODIFY
WalletWasabi.Tests/RegTests.cs
WalletWasabi.Tests/RegTests.cs
@@ -698,7 +698,10 @@ namespace WalletWasabi.Tests
finally
{
wallet.NewFilterProcessed -= Wallet_NewFilterProcessed;
- wallet?.Dispose();
+ if (wallet != null)
+ {
+ await wallet.StopAsync();
+ }
// Dispose wasabi synchronizer service.
await synchronizer?.StopAsync();
@@ -1172,7 +1175,10 @@ namespace WalletWasabi.Tests
finally
{
wallet.NewFilterProcessed -= Wallet_NewFilterProcessed;
- wallet?.Dispose();
+ if (wallet != null)
+ {
+ await wallet.StopAsync();
+ }
// Dispose wasabi synchronizer service.
await synchronizer?.StopAsync();
// Dispose connection service.
@@ -1335,7 +1341,10 @@ namespace WalletWasabi.Tests
}
finally
{
- wallet?.Dispose();
+ if (wallet != null)
+ {
+ await wallet.StopAsync();
+ }
// Dispose wasabi synchronizer service.
await synchronizer?.StopAsync();
// Dispose connection service.
@@ -1499,7 +1508,10 @@ namespace WalletWasabi.Tests
}
finally
{
- wallet?.Dispose();
+ if (wallet != null)
+ {
+ await wallet.StopAsync();
+ }
// Dispose wasabi synchronizer service.
await synchronizer?.StopAsync();
// Dispose connection service.
@@ -1665,7 +1677,10 @@ namespace WalletWasabi.Tests
}
finally
{
- wallet?.Dispose();
+ if (wallet != null)
+ {
+ await wallet.StopAsync();
+ }
// Dispose wasabi synchronizer service.
await synchronizer?.StopAsync();
// Dispose connection service.
@@ -1771,7 +1786,10 @@ namespace WalletWasabi.Tests
}
finally
{
- wallet?.Dispose();
+ if (wallet != null)
+ {
+ await wallet.StopAsync();
+ }
// Dispose wasabi synchronizer service.
await synchronizer?.StopAsync();
// Dispose connection service.
@@ -3435,7 +3453,10 @@ namespace WalletWasabi.Tests
finally
{
wallet.NewFilterProcessed -= Wallet_NewFilterProcessed;
- wallet?.Dispose();
+ if (wallet != null)
+ {
+ await wallet.StopAsync();
+ }
// Dispose connection service.
nodes?.Dispose();
// Dispose mempool serving node.
@@ -3445,7 +3466,10 @@ namespace WalletWasabi.Tests
{
await chaumianClient.StopAsync();
}
- wallet2?.Dispose();
+ if (wallet2 != null)
+ {
+ await wallet2.StopAsync();
+ }
// Dispose wasabi synchronizer service.
await synchronizer?.StopAsync();
// Dispose connection service.

MODIFY
WalletWasabi/Services/WalletService.cs
WalletWasabi/Services/WalletService.cs
@@ -29,7 +29,7 @@ using WalletWasabi.WebClients.Wasabi;
namespace WalletWasabi.Services
{
- public class WalletService : IDisposable
+ public class WalletService
{
public static event EventHandler<bool> DownloadingBlockChanged;
@@ -289,14 +289,15 @@ namespace WalletWasabi.Services
{
try
{
- IEnumerable<SmartTransaction> transactions = null;
+ SmartTransaction[] transactions = null;
try
{
string jsonString = File.ReadAllText(TransactionsFilePath, Encoding.UTF8);
transactions = JsonConvert.DeserializeObject<IEnumerable<SmartTransaction>>(jsonString)?
.Where(x => !x.Confirmed)? // Only unconfirmed ones.
.OrderBy(x => x.Height)? // Order by height first (it's mempool or unknown)
- .ThenBy(x => x.FirstSeenIfMemPoolTime ?? DateTime.UtcNow); // Order by the time of first seen.
+ .ThenBy(x => x.FirstSeenIfMemPoolTime ?? DateTime.UtcNow)
+ .ToArray(); // Order by the time of first seen.
}
catch (Exception ex)
{
@@ -309,12 +310,14 @@ namespace WalletWasabi.Services
{
try
{
+ using (await TransactionProcessingLock.LockAsync())
using (var client = new WasabiClient(Synchronizer.WasabiClient.TorClient.DestinationUriAction, Synchronizer.WasabiClient.TorClient.TorSocks5EndPoint))
{
var compactness = 10;
var mempoolHashes = await client.GetMempoolHashesAsync(compactness);
+ var cnt = 0;
foreach (var tx in transactions)
{
if (mempoolHashes.Contains(tx.GetHash().ToString().Substring(0, compactness)))
@@ -324,7 +327,15 @@ namespace WalletWasabi.Services
MemPool.TransactionHashes.TryAdd(tx.GetHash());
Logger.LogInfo<WalletService>($"Transaction was successfully tested against the backend's mempool hashes: {tx.GetHash()}.");
+ cnt++;
+ }
}
+
+ UnconfirmedTransactionsInitialized = true;
+
+ if (cnt != transactions.Count())
+ {
+ SerializeTransactionCache();
}
}
}
@@ -470,6 +481,8 @@ namespace WalletWasabi.Services
private async Task<bool> ProcessBlockAsync(Height height, Block block, IEnumerable<int> filterByTxIds = null)
{
var ret = false;
+ using (await TransactionProcessingLock.LockAsync())
+ {
if (filterByTxIds is null)
{
var relevantIndicies = new List<int>();
@@ -504,6 +517,7 @@ namespace WalletWasabi.Services
}
}
}
+ }
ProcessedBlocks.TryAdd(block.GetHash(), (height, block.Header.BlockTime));
@@ -692,6 +706,8 @@ namespace WalletWasabi.Services
return walletRelevant;
}
+ private Node _localBitcoinCoreNode = null;
+
public Node LocalBitcoinCoreNode
{
get
@@ -706,6 +722,8 @@ namespace WalletWasabi.Services
private set => _localBitcoinCoreNode = value;
}
+ public bool UnconfirmedTransactionsInitialized { get; private set; }
+
/// <exception cref="OperationCanceledException"></exception>
public async Task<Block> GetOrDownloadBlockAsync(uint256 hash, CancellationToken cancel)
{
@@ -1389,10 +1407,13 @@ namespace WalletWasabi.Services
}
}
+ private static long SendCount = 0;
+
public async Task SendTransactionAsync(SmartTransaction transaction)
{
try
{
+ Interlocked.Increment(ref SendCount);
// Broadcast to a random node.
// Wait until it arrives to at least two other nodes.
// If something's wrong, fall back broadcasting with backend.
@@ -1494,14 +1515,22 @@ namespace WalletWasabi.Services
}
}
- await ProcessTransactionAsync(new SmartTransaction(transaction.Transaction, Height.MemPool));
+ using (await TransactionProcessingLock.LockAsync())
+ {
+ if (await ProcessTransactionAsync(new SmartTransaction(transaction.Transaction, Height.MemPool)))
+ {
+ SerializeTransactionCache();
+ }
+
MemPool.TransactionHashes.TryAdd(transaction.GetHash());
+ }
Logger.LogInfo<WalletService>($"Transaction is successfully broadcasted to backend: {transaction.GetHash()}.");
}
finally
{
MemPool.TryRemoveFromBroadcastStore(transaction.GetHash(), out _); // Remove it just to be sure. Probably has been removed previously.
+ Interlocked.Decrement(ref SendCount);
}
}
@@ -1562,6 +1591,11 @@ namespace WalletWasabi.Services
private void SerializeTransactionCache()
{
+ if (!UnconfirmedTransactionsInitialized) // If unconfirmed ones are not yet initialized, then don't serialize because unconfirmed are going to be lost.
+ {
+ return;
+ }
+
IoHelpers.EnsureContainingDirectoryExists(TransactionsFilePath);
string jsonString = JsonConvert.SerializeObject(TransactionCache.OrderBy(x => x.Height).ThenBy(x => x.FirstSeenIfMemPoolTime ?? DateTime.UtcNow), Formatting.Indented);
File.WriteAllText(TransactionsFilePath,
@@ -1618,17 +1652,13 @@ namespace WalletWasabi.Services
Logger.LogInfo<WalletService>($"Current timeout value used on block download is: {timeout} seconds.");
}
- #region IDisposable Support
-
- private volatile bool _disposedValue = false; // To detect redundant calls
- private Node _localBitcoinCoreNode = null;
-
- protected virtual void Dispose(bool disposing)
- {
- if (!_disposedValue)
+ public async Task StopAsync()
{
- if (disposing)
+ while (Interlocked.Read(ref SendCount) != 0) // Make sure to wait for send to finish.
{
+ await Task.Delay(50);
+ }
+
BitcoinStore.IndexStore.NewFilter -= IndexDownloader_NewFilterAsync;
BitcoinStore.IndexStore.Reorged -= IndexDownloader_ReorgedAsync;
MemPool.TransactionReceived -= MemPool_TransactionReceivedAsync;
@@ -1636,19 +1666,5 @@ namespace WalletWasabi.Services
DisconnectDisposeNullLocalBitcoinCoreNode();
}
-
- _disposedValue = true;
- }
- }
-
- // This code added to correctly implement the disposable pattern.
- public void Dispose()
- {
- // Do not change this code. Put cleanup code in Dispose(bool disposing) above.
- Dispose(true);
- // GC.SuppressFinalize(this);
- }
-
- #endregion IDisposable Support
}
}

Don't lose unconfirmed transactions.