Promote cache to first-class citizen
MODIFY
WalletWasabi.Gui/Global.cs
WalletWasabi.Gui/Global.cs
@@ -2,6 +2,7 @@ using Avalonia;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Controls.Notifications;
using Avalonia.Threading;
+using Microsoft.Extensions.Caching.Memory;
using NBitcoin;
using NBitcoin.Protocol;
using NBitcoin.Protocol.Behaviors;
@@ -72,6 +73,8 @@ namespace WalletWasabi.Gui
public Network Network => Config.Network;
+ public MemoryCache Cache { get; private set; }
+
public static JsonRpcServer RpcServer { get; private set; }
public Global()
@@ -115,6 +118,7 @@ namespace WalletWasabi.Gui
try
{
+ Cache = new MemoryCache(new MemoryCacheOptions { SizeLimit = 1_000 });
BitcoinStore = new BitcoinStore();
var bstoreInitTask = BitcoinStore.InitializeAsync(Path.Combine(DataDir, "BitcoinStore"), Network);
var addressManagerFolderPath = Path.Combine(DataDir, "AddressManager");
@@ -198,7 +202,8 @@ namespace WalletWasabi.Gui
EndPointStrategy.Default(Network, EndPointType.Rpc),
txIndex: null,
prune: null,
- userAgent: $"/WasabiClient:{Constants.ClientVersion}/"),
+ userAgent: $"/WasabiClient:{Constants.ClientVersion}/",
+ Cache),
cancel)
.ConfigureAwait(false);
}
@@ -339,7 +344,9 @@ namespace WalletWasabi.Gui
#region Blocks provider
var blockProvider = new CachedBlockProvider(
- new SmartBlockProvider(new P2pBlockProvider(Nodes, BitcoinCoreNode, Synchronizer, Config.ServiceConfiguration, Network)),
+ new SmartBlockProvider(
+ new P2pBlockProvider(Nodes, BitcoinCoreNode, Synchronizer, Config.ServiceConfiguration, Network),
+ Cache),
new FileSystemBlockRepository(blocksFolderPath, Network));
#endregion Blocks provider
@@ -751,6 +758,12 @@ namespace WalletWasabi.Gui
Logger.LogInfo($"{nameof(TorManager)} is stopped.");
}
+ var cache = Cache;
+ if (cache is { })
+ {
+ cache.Dispose();
+ }
+
if (AsyncMutex.IsAny)
{
try

MODIFY
WalletWasabi.Tests/Helpers/TestNodeBuilder.cs
WalletWasabi.Tests/Helpers/TestNodeBuilder.cs
+using Microsoft.Extensions.Caching.Memory;
using NBitcoin;
using System;
using System.Collections.Generic;
@@ -32,7 +33,8 @@ namespace WalletWasabi.Tests.Helpers
EndPointStrategy.Random,
txIndex: 1,
prune: 0,
- userAgent: $"/WasabiClient:{Constants.ClientVersion}/"),
+ userAgent: $"/WasabiClient:{Constants.ClientVersion}/",
+ new MemoryCache(new MemoryCacheOptions())),
CancellationToken.None);
}
}

MODIFY
WalletWasabi.Tests/UnitTests/SmartBlockProviderTests.cs
WalletWasabi.Tests/UnitTests/SmartBlockProviderTests.cs
@@ -2,6 +2,7 @@ using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
+using Microsoft.Extensions.Caching.Memory;
using NBitcoin;
using WalletWasabi.Legal;
using WalletWasabi.Wallets;
@@ -27,7 +28,8 @@ namespace WalletWasabi.Tests.UnitTests
var block = Block.CreateBlock(Network.Main);
return block;
};
- var blockProvider = new SmartBlockProvider(source);
+ using var cache = new MemoryCache(new MemoryCacheOptions());
+ var blockProvider = new SmartBlockProvider(source, cache);
var b1 = blockProvider.GetBlockAsync(uint256.Zero, CancellationToken.None);
var b2 = blockProvider.GetBlockAsync(uint256.One, CancellationToken.None);
@@ -38,7 +40,7 @@ namespace WalletWasabi.Tests.UnitTests
Assert.NotEqual(await b1, await b2);
}
- class MockProvider : IBlockProvider
+ private class MockProvider : IBlockProvider
{
public Func<uint256, CancellationToken, Task<Block>> OnGetBlockAsync { get; set; }

MODIFY
WalletWasabi/BitcoinCore/CoreNode.cs
WalletWasabi/BitcoinCore/CoreNode.cs
@@ -95,7 +95,7 @@ namespace WalletWasabi.BitcoinCore
$"{authString}",
coreNode.RpcEndPoint.ToString(coreNode.Network.DefaultPort),
coreNode.Network);
- coreNode.RpcClient = new CachedRpcClient(rpcClient, new MemoryCache(new MemoryCacheOptions { SizeLimit = 1_000 }));
+ coreNode.RpcClient = new CachedRpcClient(rpcClient, coreNodeParams.Cache);
if (coreNodeParams.TryRestart)

MODIFY
WalletWasabi/BitcoinCore/CoreNodeParams.cs
WalletWasabi/BitcoinCore/CoreNodeParams.cs
+using Microsoft.Extensions.Caching.Memory;
using NBitcoin;
using System;
using System.Collections.Generic;
@@ -23,7 +24,8 @@ namespace WalletWasabi.BitcoinCore
EndPointStrategy rpcEndPointStrategy,
int? txIndex,
int? prune,
- string userAgent)
+ string userAgent,
+ IMemoryCache cache)
{
Network = Guard.NotNull(nameof(network), network);
MempoolService = Guard.NotNull(nameof(mempoolService), mempoolService);
@@ -36,6 +38,7 @@ namespace WalletWasabi.BitcoinCore
TxIndex = txIndex;
Prune = prune;
UserAgent = Guard.NotNullOrEmptyOrWhitespace(nameof(userAgent), userAgent, trim: true);
+ Cache = Guard.NotNull(nameof(cache), cache);
}
public string DataDir { get; }
@@ -49,5 +52,6 @@ namespace WalletWasabi.BitcoinCore
public string UserAgent { get; }
public EndPointStrategy P2pEndPointStrategy { get; }
public EndPointStrategy RpcEndPointStrategy { get; }
+ public IMemoryCache Cache { get; }
}
}

MODIFY
WalletWasabi/Wallets/SmartBlockProvider.cs
WalletWasabi/Wallets/SmartBlockProvider.cs
+using Microsoft.Extensions.Caching.Memory;
using NBitcoin;
-using NBitcoin.Protocol;
-using NBitcoin.Protocol.Behaviors;
using Nito.AsyncEx;
using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Net.Sockets;
using System.Threading;
using System.Threading.Tasks;
-using WalletWasabi.BitcoinCore;
-using WalletWasabi.Helpers;
-using WalletWasabi.Logging;
-using WalletWasabi.Models;
-using WalletWasabi.Services;
namespace WalletWasabi.Wallets
{
@@ -22,25 +13,34 @@ namespace WalletWasabi.Wallets
/// </summary>
public class SmartBlockProvider : IBlockProvider
{
- public SmartBlockProvider(IBlockProvider provider)
+ public SmartBlockProvider(IBlockProvider provider, IMemoryCache cache)
{
InnerBlockProvider = provider;
+ Cache = cache;
}
- private AsyncLock Lock { get; } = new AsyncLock();
+ private IBlockProvider InnerBlockProvider { get; }
- private Dictionary<uint256, Task<Block>> Requests = new Dictionary<uint256, Task<Block>>();
+ public IMemoryCache Cache { get; }
- private IBlockProvider InnerBlockProvider { get; }
+ private AsyncLock Lock { get; } = new AsyncLock();
- public Task<Block> GetBlockAsync(uint256 hash, CancellationToken cancel)
+ public Task<Block> GetBlockAsync(uint256 blockHash, CancellationToken cancel)
{
lock (Lock)
{
- if (!Requests.TryGetValue(hash, out var getBlockTask))
+ string cacheKey = $"{nameof(SmartBlockProvider):nameof(GetBlockAsync)}:{blockHash}";
+ if (!Cache.TryGetValue(cacheKey, out Task<Block> getBlockTask))
{
- getBlockTask = InnerBlockProvider.GetBlockAsync(hash, cancel);
- Requests.Add(hash, getBlockTask);
+ getBlockTask = InnerBlockProvider.GetBlockAsync(blockHash, cancel);
+
+ var cacheEntryOptions = new MemoryCacheEntryOptions()
+ .SetSize(10)
+ // There is a block every 10 minutes in average so, keep in cache for 4 seconds.
+ .SetAbsoluteExpiration(TimeSpan.FromSeconds(4));
+
+ // Save data in cache.
+ Cache.Set(cacheKey, getBlockTask, cacheEntryOptions);
}
return getBlockTask;
}

Promote cache to first-class citizen