Add back mempool hashes query work
MODIFY
WalletWasabi.Backend/Controllers/BlockchainController.cs
WalletWasabi.Backend/Controllers/BlockchainController.cs
@@ -129,6 +129,27 @@ namespace WalletWasabi.Backend.Controllers
return Ok(estimation.Estimations);
}
+ /// <summary>
+ /// Gets mempool hashes.
+ /// </summary>
+ /// <returns>A collection of transaction hashes.</returns>
+ /// <response code="200">A collection of transaction hashes.</response>
+ /// <response code="404">Invalid model state.</response>
+ [HttpGet("mempool-hashes")]
+ [ProducesResponseType(200)]
+ [ProducesResponseType(404)]
+ public async Task<IActionResult> GetMempoolHashesAsync()
+ {
+ if (!ModelState.IsValid)
+ {
+ return BadRequest("Invalid model state.");
+ }
+
+ var transactionHashes = await Global.RpcClient.GetRawMempoolAsync();
+
+ return Ok(transactionHashes.Select(x => x.ToString()));
+ }
+
internal async Task<AllFeeEstimate> GetAllFeeEstimateAsync(EstimateSmartFeeMode mode)
{
var cacheKey = $"{nameof(GetAllFeeEstimateAsync)}_{mode}";

MODIFY
WalletWasabi/Services/MemPoolService.cs
WalletWasabi/Services/MemPoolService.cs
@@ -26,6 +26,32 @@ namespace WalletWasabi.Services
private long _cleanupInProcess;
+ public void TryPerformMempoolCleanup(IEnumerable<uint256> allMempoolHashes)
+ {
+ if (Interlocked.Read(ref _cleanupInProcess) == 1) return; // If already cleaning, then no need to run it that often.
+ Interlocked.Exchange(ref _cleanupInProcess, 1);
+ // This function is designed to prevent forever growing mempool.
+ try
+ {
+ Logger.LogInfo<MemPoolService>("Start cleaning out mempool...");
+
+ uint256[] toRemove = TransactionHashes.Except(allMempoolHashes).ToArray();
+ foreach (uint256 tx in toRemove)
+ {
+ TransactionHashes.TryRemove(tx);
+ }
+ Logger.LogInfo<MemPoolService>($"{toRemove.Count()} transactions were cleaned from mempool.");
+ }
+ catch (Exception ex)
+ {
+ Logger.LogWarning<MemPoolService>(ex);
+ }
+ finally
+ {
+ Interlocked.Exchange(ref _cleanupInProcess, 0);
+ }
+ }
+
public void TryPerformMempoolCleanupAsync(NodesGroup nodes, CancellationToken cancel)
{
if (Interlocked.Read(ref _cleanupInProcess) == 1) return; // If already cleaning, then no need to run it that often.
@@ -80,7 +106,7 @@ namespace WalletWasabi.Services
{
TransactionHashes.TryRemove(tx);
}
- Logger.LogInfo<MemPoolService>($"{toRemove.Count()} transactions were cleaned from mempool...");
+ Logger.LogInfo<MemPoolService>($"{toRemove.Count()} transactions were cleaned from mempool.");
}
catch (Exception ex)
{

MODIFY
WalletWasabi/Services/WalletService.cs
WalletWasabi/Services/WalletService.cs
@@ -229,7 +229,11 @@ namespace WalletWasabi.Services
// Try perform mempool cleanup based on connected nodes' mempools.
if (Synchronizer != null && Synchronizer.GetFiltersLeft() == 0)
{
- MemPool?.TryPerformMempoolCleanupAsync(Nodes, CancellationToken.None);
+ using (var client = new WasabiClient(Synchronizer.WasabiClient.TorClient.DestinationUriAction, Synchronizer.WasabiClient.TorClient.TorSocks5EndPoint))
+ {
+ var mempoolHashes = await client.GetMempoolHashesAsync();
+ MemPool?.TryPerformMempoolCleanup(mempoolHashes);
+ }
}
}
catch (Exception ex)
@@ -257,45 +261,50 @@ namespace WalletWasabi.Services
// Load in dummy mempool
if (File.Exists(TransactionsFilePath))
{
- var deleteTxFile = false;
try
{
- string jsonString = File.ReadAllText(TransactionsFilePath, Encoding.UTF8);
- var serializedTransactions = JsonConvert.DeserializeObject<IEnumerable<SmartTransaction>>(jsonString);
-
- foreach (SmartTransaction tx in serializedTransactions.Where(x => !x.Confirmed && !TransactionCache.Contains(x)).OrderBy(x => x.Height).ThenBy(x => x.FirstSeenIfMemPoolTime ?? DateTime.UtcNow))
- {
+ IEnumerable<SmartTransaction> transactions = null;
try
{
- await SendTransactionAsync(tx);
+ 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.
}
catch (Exception ex)
{
- deleteTxFile = true;
Logger.LogWarning<WalletService>(ex);
+ Logger.LogWarning<WalletService>($"Transaction cache got corrupted. Deleting {TransactionsFilePath}.");
+ File.Delete(TransactionsFilePath);
}
- }
- }
- catch (Exception ex)
+
+ if (transactions is null || !transactions.Any()) return;
+
+ using (var client = new WasabiClient(Synchronizer.WasabiClient.TorClient.DestinationUriAction, Synchronizer.WasabiClient.TorClient.TorSocks5EndPoint))
{
- deleteTxFile = true;
- Logger.LogWarning<WalletService>(ex);
- }
+ var mempoolHashes = await client.GetMempoolHashesAsync();
+ var mempoolSet = mempoolHashes.ToHashSet();
- if (deleteTxFile)
+ foreach (var tx in transactions)
{
- try
+ if (mempoolSet.Contains(tx.GetHash()))
{
- File.Delete(TransactionsFilePath);
+ tx.SetHeight(Height.MemPool);
+ ProcessTransaction(tx);
+ MemPool.TransactionHashes.TryAdd(tx.GetHash());
+
+ Logger.LogInfo<WalletService>($"Transaction is tested against the backend's transaction hahses set: {tx.GetHash()}.");
+ }
+ }
+ }
}
catch (Exception ex)
{
- // Don't fail because of this. It's not important.
Logger.LogWarning<WalletService>(ex);
}
}
}
- }
Coins.CollectionChanged += Coins_CollectionChanged;
#pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
RefreshCoinsHistoriesAsync();

MODIFY
WalletWasabi/WebClients/Wasabi/WasabiClient.cs
WalletWasabi/WebClients/Wasabi/WasabiClient.cs
@@ -186,6 +186,26 @@ namespace WalletWasabi.WebClients.Wasabi
return (backendCompatible, clientUpToDate);
}
+ public async Task<IEnumerable<uint256>> GetMempoolHashesAsync(CancellationToken cancel = default)
+ {
+ using (var response = await TorClient.SendAndRetryAsync(HttpMethod.Get,
+ HttpStatusCode.OK,
+ $"/api/v{Helpers.Constants.BackendMajorVersion}/btc/blockchain/mempool-hashes",
+ cancel: cancel))
+ {
+ if (response.StatusCode != HttpStatusCode.OK)
+ {
+ await response.ThrowRequestExceptionFromContentAsync();
+ }
+
+ using (HttpContent content = response.Content)
+ {
+ var ret = await content.ReadAsJsonAsync<IEnumerable<uint256>>();
+ return ret;
+ }
+ }
+ }
+
#endregion software
}
}

Add back mempool hashes query work