base refactor
MODIFY
WalletWasabi.Fluent/Global.cs
WalletWasabi.Fluent/Global.cs
@@ -188,11 +188,11 @@ public class Global
await StartRpcServerAsync(terminateService, cancel).ConfigureAwait(false);
- var blockProvider = new CachedBlockProvider(
- new SmartBlockProvider(
- new P2pBlockProvider(HostedServices.Get<P2pNetwork>().Nodes, BitcoinCoreNode, HttpClientFactory, Config.ServiceConfiguration, Network),
- Cache),
- BitcoinStore.BlockRepository);
+ var blockProvider = new SmartBlockProvider(
+ new CachedBlockProvider(BitcoinStore.BlockRepository),
+ new LocalBlockProvider(BitcoinCoreNode, HttpClientFactory, Config.ServiceConfiguration, Network),
+ new P2pBlockProvider(HostedServices.Get<P2pNetwork>().Nodes, HttpClientFactory, Config.ServiceConfiguration, Network),
+ Cache);
WalletManager.RegisterServices(BitcoinStore, Synchronizer, Config.ServiceConfiguration, HostedServices.Get<HybridFeeProvider>(), blockProvider);
}

MODIFY
WalletWasabi.Tests/IntegrationTests/P2pTests.cs
WalletWasabi.Tests/IntegrationTests/P2pTests.cs
@@ -6,6 +6,7 @@ using System.IO;
using System.Net;
using System.Threading;
using System.Threading.Tasks;
+using Microsoft.Extensions.Caching.Memory;
using WalletWasabi.Blockchain.Analysis.FeesEstimation;
using WalletWasabi.Blockchain.Blocks;
using WalletWasabi.Blockchain.Keys;
@@ -99,9 +100,17 @@ public class P2pTests
var feeProvider = new HybridFeeProvider(synchronizer, null);
ServiceConfiguration serviceConfig = new(new IPEndPoint(IPAddress.Loopback, network.DefaultPort), Money.Coins(Constants.DefaultDustThreshold));
- CachedBlockProvider blockProvider = new(
- new P2pBlockProvider(nodes, null, httpClientFactory, serviceConfig, network),
- bitcoinStore.BlockRepository);
+ var cache = new MemoryCache(new MemoryCacheOptions
+ {
+ SizeLimit = 1_000,
+ ExpirationScanFrequency = TimeSpan.FromSeconds(30)
+ });
+
+ var blockProvider = new SmartBlockProvider(
+ new CachedBlockProvider(bitcoinStore.BlockRepository),
+ new LocalBlockProvider(null, httpClientFactory, serviceConfig, network),
+ new P2pBlockProvider(nodes, httpClientFactory, serviceConfig, network),
+ cache);
using Wallet wallet = Wallet.CreateAndRegisterServices(
network,
@@ -152,7 +161,7 @@ public class P2pTests
// So next test will download the block.
foreach (var hash in blocksToDownload)
{
- await blockProvider.BlockRepository.RemoveAsync(hash, CancellationToken.None);
+ await blockProvider.InvalidateAsync(hash, CancellationToken.None);
}
if (wallet is { })
{
@@ -167,6 +176,7 @@ public class P2pTests
IoHelpers.EnsureContainingDirectoryExists(addressManagerFilePath);
addressManager?.SavePeerFile(addressManagerFilePath, network);
Logger.LogInfo($"Saved {nameof(AddressManager)} to `{addressManagerFilePath}`.");
+ cache.Dispose();
}
}
}

MODIFY
WalletWasabi.Tests/RegressionTests/BuildTests.cs
WalletWasabi.Tests/RegressionTests/BuildTests.cs
@@ -5,6 +5,7 @@ using System.Linq;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
+using Microsoft.Extensions.Caching.Memory;
using WalletWasabi.BitcoinCore.Rpc;
using WalletWasabi.Blockchain.Analysis.FeesEstimation;
using WalletWasabi.Blockchain.Keys;
@@ -60,9 +61,17 @@ public class BuildTests
// 5. Create wallet service.
var workDir = Helpers.Common.GetWorkDir();
- CachedBlockProvider blockProvider = new(
- new P2pBlockProvider(nodes, null, httpClientFactory, serviceConfiguration, network),
- bitcoinStore.BlockRepository);
+ var cache = new MemoryCache(new MemoryCacheOptions
+ {
+ SizeLimit = 1_000,
+ ExpirationScanFrequency = TimeSpan.FromSeconds(30)
+ });
+
+ var blockProvider = new SmartBlockProvider(
+ new CachedBlockProvider(bitcoinStore.BlockRepository),
+ new LocalBlockProvider(null, httpClientFactory, serviceConfiguration, network),
+ new P2pBlockProvider(nodes, httpClientFactory, serviceConfiguration, network),
+ cache);
using var wallet = Wallet.CreateAndRegisterServices(network, bitcoinStore, keyManager, synchronizer, workDir, serviceConfiguration, feeProvider, blockProvider);
wallet.NewFilterProcessed += Common.Wallet_NewFilterProcessed;
@@ -213,9 +222,18 @@ public class BuildTests
// 5. Create wallet service.
var workDir = Helpers.Common.GetWorkDir();
- CachedBlockProvider blockProvider = new(
- new P2pBlockProvider(nodes, null, httpClientFactory, serviceConfiguration, network),
- bitcoinStore.BlockRepository);
+ var cache = new MemoryCache(new MemoryCacheOptions
+ {
+ SizeLimit = 1_000,
+ ExpirationScanFrequency = TimeSpan.FromSeconds(30)
+ });
+
+ var blockProvider = new SmartBlockProvider(
+ new CachedBlockProvider(bitcoinStore.BlockRepository),
+ new LocalBlockProvider(null, httpClientFactory, serviceConfiguration, network),
+ new P2pBlockProvider(nodes, httpClientFactory, serviceConfiguration, network),
+ cache);
+
WalletManager walletManager = new(network, workDir, new WalletDirectories(network, workDir));
walletManager.RegisterServices(bitcoinStore, synchronizer, serviceConfiguration, feeProvider, blockProvider);

MODIFY
WalletWasabi.Tests/RegressionTests/SendTests.cs
WalletWasabi.Tests/RegressionTests/SendTests.cs
@@ -4,6 +4,7 @@ using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
+using Microsoft.Extensions.Caching.Memory;
using WalletWasabi.BitcoinCore.Rpc;
using WalletWasabi.Blockchain.Analysis.FeesEstimation;
using WalletWasabi.Blockchain.Keys;
@@ -59,9 +60,17 @@ public class SendTests
// 5. Create wallet service.
var workDir = Helpers.Common.GetWorkDir();
- CachedBlockProvider blockProvider = new(
- new P2pBlockProvider(nodes, null, httpClientFactory, serviceConfiguration, network),
- bitcoinStore.BlockRepository);
+ var cache = new MemoryCache(new MemoryCacheOptions
+ {
+ SizeLimit = 1_000,
+ ExpirationScanFrequency = TimeSpan.FromSeconds(30)
+ });
+
+ var blockProvider = new SmartBlockProvider(
+ new CachedBlockProvider(bitcoinStore.BlockRepository),
+ new LocalBlockProvider(null, httpClientFactory, serviceConfiguration, network),
+ new P2pBlockProvider(nodes, httpClientFactory, serviceConfiguration, network),
+ cache);
WalletManager walletManager = new(network, workDir, new WalletDirectories(network, workDir));
walletManager.RegisterServices(bitcoinStore, synchronizer, serviceConfiguration, feeProvider, blockProvider);
@@ -537,9 +546,17 @@ public class SendTests
// 5. Create wallet service.
var workDir = Helpers.Common.GetWorkDir();
- CachedBlockProvider blockProvider = new(
- new P2pBlockProvider(nodes, null, httpClientFactory, serviceConfiguration, network),
- bitcoinStore.BlockRepository);
+ var cache = new MemoryCache(new MemoryCacheOptions
+ {
+ SizeLimit = 1_000,
+ ExpirationScanFrequency = TimeSpan.FromSeconds(30)
+ });
+
+ var blockProvider = new SmartBlockProvider(
+ new CachedBlockProvider(bitcoinStore.BlockRepository),
+ new LocalBlockProvider(null, httpClientFactory, serviceConfiguration, network),
+ new P2pBlockProvider(nodes, httpClientFactory, serviceConfiguration, network),
+ cache);
WalletManager walletManager = new(network, workDir, new WalletDirectories(network, workDir));
walletManager.RegisterServices(bitcoinStore, synchronizer, serviceConfiguration, feeProvider, blockProvider);
@@ -711,9 +728,17 @@ public class SendTests
// 5. Create wallet service.
var workDir = Helpers.Common.GetWorkDir();
- CachedBlockProvider blockProvider = new(
- new P2pBlockProvider(nodes, null, httpClientFactory, serviceConfiguration, network),
- bitcoinStore.BlockRepository);
+ var cache = new MemoryCache(new MemoryCacheOptions
+ {
+ SizeLimit = 1_000,
+ ExpirationScanFrequency = TimeSpan.FromSeconds(30)
+ });
+
+ var blockProvider = new SmartBlockProvider(
+ new CachedBlockProvider(bitcoinStore.BlockRepository),
+ new LocalBlockProvider(null, httpClientFactory, serviceConfiguration, network),
+ new P2pBlockProvider(nodes, httpClientFactory, serviceConfiguration, network),
+ cache);
using var wallet = Wallet.CreateAndRegisterServices(network, bitcoinStore, keyManager, synchronizer, workDir, serviceConfiguration, feeProvider, blockProvider);
wallet.NewFilterProcessed += Common.Wallet_NewFilterProcessed;

MODIFY
WalletWasabi.Tests/RegressionTests/WalletTests.cs
WalletWasabi.Tests/RegressionTests/WalletTests.cs
@@ -4,6 +4,7 @@ using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
+using Microsoft.Extensions.Caching.Memory;
using WalletWasabi.Backend.Models;
using WalletWasabi.BitcoinCore.Rpc;
using WalletWasabi.Blockchain.Analysis.Clustering;
@@ -247,9 +248,17 @@ public class WalletTests
// 4. Create wallet service.
var workDir = Helpers.Common.GetWorkDir();
- CachedBlockProvider blockProvider = new(
- new P2pBlockProvider(nodes, null, httpClientFactory, serviceConfiguration, network),
- bitcoinStore.BlockRepository);
+ var cache = new MemoryCache(new MemoryCacheOptions
+ {
+ SizeLimit = 1_000,
+ ExpirationScanFrequency = TimeSpan.FromSeconds(30)
+ });
+
+ var blockProvider = new SmartBlockProvider(
+ new CachedBlockProvider(bitcoinStore.BlockRepository),
+ new LocalBlockProvider(null, httpClientFactory, serviceConfiguration, network),
+ new P2pBlockProvider(nodes, httpClientFactory, serviceConfiguration, network),
+ cache);
using var wallet = Wallet.CreateAndRegisterServices(network, bitcoinStore, keyManager, synchronizer, workDir, serviceConfiguration, feeProvider, blockProvider);
wallet.NewFilterProcessed += Common.Wallet_NewFilterProcessed;

MODIFY
WalletWasabi.Tests/UnitTests/SmartBlockProviderTests.cs
WalletWasabi.Tests/UnitTests/SmartBlockProviderTests.cs
@@ -10,6 +10,7 @@ namespace WalletWasabi.Tests.UnitTests;
public class SmartBlockProviderTests
{
+ /*
/// <summary>
/// Tests <see cref="SmartBlockProvider.GetBlockAsync(uint256, CancellationToken)"/> behavior.
/// </summary>
@@ -40,4 +41,5 @@ public class SmartBlockProviderTests
Assert.NotEqual(blocks[0], result[1]);
}
+ */
}

MODIFY
WalletWasabi.Tests/UnitTests/TestBlockProvider.cs
WalletWasabi.Tests/UnitTests/TestBlockProvider.cs
@@ -15,8 +15,8 @@ public class TestBlockProvider : IBlockProvider
private Dictionary<uint256, Block> Blocks { get; }
- public Task<Block> GetBlockAsync(uint256 hash, CancellationToken cancel)
+ public Task<Block?> TryGetBlockAsync(uint256 hash, CancellationToken cancel)
{
- return Task.FromResult(Blocks[hash]);
+ return Task.FromResult(Blocks[hash])!;
}
}

MODIFY
WalletWasabi/Wallets/CachedBlockProvider.cs
WalletWasabi/Wallets/CachedBlockProvider.cs
@@ -9,14 +9,12 @@ namespace WalletWasabi.Wallets;
/// </summary>
public class CachedBlockProvider : IBlockProvider
{
- public CachedBlockProvider(IBlockProvider blockSourceProvider, IRepository<uint256, Block> blockRepository)
+ public CachedBlockProvider(IRepository<uint256, Block> blockRepository)
{
BlockRepository = blockRepository;
- BlockSourceProvider = blockSourceProvider;
}
public IRepository<uint256, Block> BlockRepository { get; }
- public IBlockProvider BlockSourceProvider { get; }
/// <summary>
/// Gets a bitcoin block. In case the requested block is not available in the repository it is returned
@@ -26,16 +24,15 @@ public class CachedBlockProvider : IBlockProvider
/// <param name="hash">The block's hash that identifies the requested block.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>The requested bitcoin block.</returns>
- public async Task<Block> GetBlockAsync(uint256 hash, CancellationToken cancellationToken)
+ public async Task<Block?> TryGetBlockAsync(uint256 hash, CancellationToken cancellationToken)
{
- Block? block = await BlockRepository.TryGetAsync(hash, cancellationToken).ConfigureAwait(false);
- if (block is null)
+ return await BlockRepository.TryGetAsync(hash, cancellationToken).ConfigureAwait(false);
+ }
+
+ public async Task SaveBlockAsync(Block block, CancellationToken cancellationToken)
{
- block = await BlockSourceProvider.GetBlockAsync(hash, cancellationToken).ConfigureAwait(false);
await BlockRepository.SaveAsync(block, cancellationToken).ConfigureAwait(false);
}
- return block;
- }
/// <summary>
/// Removes the specified block from the cache repository.

MODIFY
WalletWasabi/Wallets/IBlockProvider.cs
WalletWasabi/Wallets/IBlockProvider.cs
@@ -9,5 +9,5 @@ namespace WalletWasabi.Wallets;
/// </summary>
public interface IBlockProvider
{
- Task<Block> GetBlockAsync(uint256 hash, CancellationToken cancel);
+ Task<Block?> TryGetBlockAsync(uint256 hash, CancellationToken cancel);
}

ADD
WalletWasabi/Wallets/LocalBlockProvider.cs
+using System.Net.Sockets;
+using System.Threading;
+using System.Threading.Tasks;
+using NBitcoin;
+using NBitcoin.Protocol;
+using NBitcoin.Protocol.Behaviors;
+using WalletWasabi.BitcoinCore;
+using WalletWasabi.Extensions;
+using WalletWasabi.Helpers;
+using WalletWasabi.Logging;
+using WalletWasabi.Models;
+using WalletWasabi.WebClients.Wasabi;
+
+namespace WalletWasabi.Wallets;
+
+public class LocalBlockProvider : IBlockProvider
+{
+
+ public LocalBlockProvider(CoreNode? coreNode, HttpClientFactory httpClientFactory, ServiceConfiguration serviceConfiguration, Network network)
+ {
+ CoreNode = coreNode;
+ Network = network;
+ ServiceConfiguration = serviceConfiguration;
+ HttpClientFactory = httpClientFactory;
+ }
+
+ private Network Network { get; }
+ private CoreNode? CoreNode { get; }
+ private Node? LocalBitcoinCoreNode { get; set; }
+ private ServiceConfiguration ServiceConfiguration { get; }
+ private HttpClientFactory HttpClientFactory { get; }
+
+ public async Task<Block?> TryGetBlockAsync(uint256 hash, CancellationToken cancellationToken)
+ {
+ Block? rpcResult = null;
+
+ if (CoreNode?.RpcClient is not null)
+ {
+ try
+ {
+ rpcResult = await GetBlockFromRpcAsync(hash, cancellationToken).ConfigureAwait(false);
+ }
+ catch (Exception ex)
+ {
+ Logger.LogDebug(ex);
+ }
+ }
+
+ return rpcResult ?? await GetBlockFromLocalNodeAsync(hash, cancellationToken).ConfigureAwait(false);
+ }
+
+ private async Task<Block?> GetBlockFromRpcAsync(uint256 hash, CancellationToken cancellationToken)
+ {
+ try
+ {
+ var block = await CoreNode!.RpcClient.GetBlockAsync(hash, cancellationToken).ConfigureAwait(false);
+ return block;
+ }
+ catch (Exception ex)
+ {
+ Logger.LogDebug(ex);
+ return null;
+ }
+ }
+
+ private async Task<Block?> GetBlockFromLocalNodeAsync(uint256 hash, CancellationToken cancellationToken)
+ {
+ try
+ {
+ if (LocalBitcoinCoreNode is null || (!LocalBitcoinCoreNode.IsConnected && Network != Network.RegTest)) // If RegTest then we're already connected do not try again.
+ {
+ DisconnectDisposeNullLocalBitcoinCoreNode();
+ using var handshakeTimeout = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
+ handshakeTimeout.CancelAfter(TimeSpan.FromSeconds(10));
+ var nodeConnectionParameters = new NodeConnectionParameters()
+ {
+ ConnectCancellation = handshakeTimeout.Token,
+ IsRelay = false,
+ UserAgent = $"/Wasabi:{Constants.ClientVersion}/"
+ };
+
+ // If an onion was added must try to use Tor.
+ // onlyForOnionHosts should connect to it if it's an onion endpoint automatically and non-Tor endpoints through clearnet/localhost
+ if (HttpClientFactory.IsTorEnabled)
+ {
+ nodeConnectionParameters.TemplateBehaviors.Add(new SocksSettingsBehavior(HttpClientFactory.TorEndpoint, onlyForOnionHosts: true, networkCredential: null, streamIsolation: false));
+ }
+
+ var localEndPoint = ServiceConfiguration.BitcoinCoreEndPoint;
+ var localNode = await Node.ConnectAsync(Network, localEndPoint, nodeConnectionParameters).ConfigureAwait(false);
+ try
+ {
+ Logger.LogInfo("TCP Connection succeeded, handshaking...");
+ localNode.VersionHandshake(Constants.LocalNodeRequirements, handshakeTimeout.Token);
+ var peerServices = localNode.PeerVersion.Services;
+
+ Logger.LogInfo("Handshake completed successfully.");
+
+ if (!localNode.IsConnected)
+ {
+ throw new InvalidOperationException($"Wasabi could not complete the handshake with the local node and dropped the connection.{Environment.NewLine}" +
+ "Probably this is because the node does not support retrieving full blocks or segwit serialization.");
+ }
+ LocalBitcoinCoreNode = localNode;
+ }
+ catch (OperationCanceledException) when (handshakeTimeout.IsCancellationRequested)
+ {
+ Logger.LogWarning($"Wasabi could not complete the handshake with the local node. Probably Wasabi is not whitelisted by the node.{Environment.NewLine}" +
+ "Use \"whitebind\" in the node configuration. (Typically whitebind=127.0.0.1:8333 if Wasabi and the node are on the same machine and whitelist=1.2.3.4 if they are not.)");
+ throw;
+ }
+ }
+
+ // Get Block from local node
+ Block blockFromLocalNode;
+ // Should timeout faster. Not sure if it should ever fail though. Maybe let's keep like this later for remote node connection.
+ using (var cts = new CancellationTokenSource(TimeSpan.FromSeconds(64)))
+ {
+ blockFromLocalNode = await LocalBitcoinCoreNode.DownloadBlockAsync(hash, cts.Token).ConfigureAwait(false);
+ }
+
+ // Validate retrieved block
+ if (!blockFromLocalNode.Check())
+ {
+ throw new InvalidOperationException("Disconnected node, because invalid block received!");
+ }
+
+ // Retrieved block from local node and block is valid
+ Logger.LogInfo($"Block acquired from local P2P connection: {hash}.");
+ return blockFromLocalNode;
+ }
+ catch (Exception ex)
+ {
+ DisconnectDisposeNullLocalBitcoinCoreNode();
+
+ if (ex is SocketException)
+ {
+ Logger.LogTrace("Did not find local listening and running full node instance. Trying to fetch needed block from other source.");
+ }
+ else
+ {
+ Logger.LogWarning(ex);
+ }
+ }
+ return null;
+ }
+ private void DisconnectDisposeNullLocalBitcoinCoreNode()
+ {
+ if (LocalBitcoinCoreNode is { })
+ {
+ try
+ {
+ LocalBitcoinCoreNode?.Disconnect();
+ }
+ catch (Exception ex)
+ {
+ Logger.LogDebug(ex);
+ }
+ finally
+ {
+ try
+ {
+ LocalBitcoinCoreNode?.Dispose();
+ }
+ catch (Exception ex)
+ {
+ Logger.LogDebug(ex);
+ }
+ finally
+ {
+ LocalBitcoinCoreNode = null;
+ Logger.LogInfo($"Local {Constants.BuiltinBitcoinNodeName} node disconnected.");
+ }
+ }
+ }
+ }
+}

MODIFY
WalletWasabi/Wallets/P2pBlockProvider.cs
WalletWasabi/Wallets/P2pBlockProvider.cs
@@ -19,37 +19,19 @@ namespace WalletWasabi.Wallets;
/// </summary>
public class P2pBlockProvider : IBlockProvider
{
- private Node? _localBitcoinCoreNode = null;
-
- public P2pBlockProvider(NodesGroup nodes, CoreNode? coreNode, HttpClientFactory httpClientFactory, ServiceConfiguration serviceConfiguration, Network network)
+ public P2pBlockProvider(NodesGroup nodes, HttpClientFactory httpClientFactory, ServiceConfiguration serviceConfiguration, Network network)
{
Nodes = nodes;
- CoreNode = coreNode;
HttpClientFactory = httpClientFactory;
ServiceConfiguration = serviceConfiguration;
Network = network;
}
private NodesGroup Nodes { get; }
- private CoreNode? CoreNode { get; }
private HttpClientFactory HttpClientFactory { get; }
private ServiceConfiguration ServiceConfiguration { get; }
private Network Network { get; }
- private Node? LocalBitcoinCoreNode
- {
- get
- {
- if (Network == Network.RegTest)
- {
- return Nodes.ConnectedNodes.First();
- }
-
- return _localBitcoinCoreNode;
- }
- set => _localBitcoinCoreNode = value;
- }
-
private int NodeTimeouts { get; set; }
/// <summary>
@@ -59,7 +41,7 @@ public class P2pBlockProvider : IBlockProvider
/// <param name="hash">The block's hash that identifies the requested block.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>The requested bitcoin block.</returns>
- public async Task<Block> GetBlockAsync(uint256 hash, CancellationToken cancellationToken)
+ public async Task<Block?> TryGetBlockAsync(uint256 hash, CancellationToken cancellationToken)
{
Block? block;
@@ -68,14 +50,6 @@ public class P2pBlockProvider : IBlockProvider
cancellationToken.ThrowIfCancellationRequested();
try
{
- // Try to get block information from local running Core node first.
- block = await TryDownloadBlockFromLocalNodeAsync(hash, cancellationToken).ConfigureAwait(false);
-
- if (block is { })
- {
- break;
- }
-
// If no connection, wait, then continue.
while (Nodes.ConnectedNodes.Count == 0)
{
@@ -140,136 +114,6 @@ public class P2pBlockProvider : IBlockProvider
return block;
}
- private async Task<Block?> TryDownloadBlockFromLocalNodeAsync(uint256 hash, CancellationToken cancellationToken)
- {
- if (CoreNode?.RpcClient is null)
- {
- try
- {
- if (LocalBitcoinCoreNode is null || (!LocalBitcoinCoreNode.IsConnected && Network != Network.RegTest)) // If RegTest then we're already connected do not try again.
- {
- DisconnectDisposeNullLocalBitcoinCoreNode();
- using var handshakeTimeout = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
- handshakeTimeout.CancelAfter(TimeSpan.FromSeconds(10));
- var nodeConnectionParameters = new NodeConnectionParameters()
- {
- ConnectCancellation = handshakeTimeout.Token,
- IsRelay = false,
- UserAgent = $"/Wasabi:{Constants.ClientVersion}/"
- };
-
- // If an onion was added must try to use Tor.
- // onlyForOnionHosts should connect to it if it's an onion endpoint automatically and non-Tor endpoints through clearnet/localhost
- if (HttpClientFactory.IsTorEnabled)
- {
- nodeConnectionParameters.TemplateBehaviors.Add(new SocksSettingsBehavior(HttpClientFactory.TorEndpoint, onlyForOnionHosts: true, networkCredential: null, streamIsolation: false));
- }
-
- var localEndPoint = ServiceConfiguration.BitcoinCoreEndPoint;
- var localNode = await Node.ConnectAsync(Network, localEndPoint, nodeConnectionParameters).ConfigureAwait(false);
- try
- {
- Logger.LogInfo("TCP Connection succeeded, handshaking...");
- localNode.VersionHandshake(Constants.LocalNodeRequirements, handshakeTimeout.Token);
- var peerServices = localNode.PeerVersion.Services;
-
- Logger.LogInfo("Handshake completed successfully.");
-
- if (!localNode.IsConnected)
- {
- throw new InvalidOperationException($"Wasabi could not complete the handshake with the local node and dropped the connection.{Environment.NewLine}" +
- "Probably this is because the node does not support retrieving full blocks or segwit serialization.");
- }
- LocalBitcoinCoreNode = localNode;
- }
- catch (OperationCanceledException) when (handshakeTimeout.IsCancellationRequested)
- {
- Logger.LogWarning($"Wasabi could not complete the handshake with the local node. Probably Wasabi is not whitelisted by the node.{Environment.NewLine}" +
- "Use \"whitebind\" in the node configuration. (Typically whitebind=127.0.0.1:8333 if Wasabi and the node are on the same machine and whitelist=1.2.3.4 if they are not.)");
- throw;
- }
- }
-
- // Get Block from local node
- Block blockFromLocalNode;
- // Should timeout faster. Not sure if it should ever fail though. Maybe let's keep like this later for remote node connection.
- using (var cts = new CancellationTokenSource(TimeSpan.FromSeconds(64)))
- {
- blockFromLocalNode = await LocalBitcoinCoreNode.DownloadBlockAsync(hash, cts.Token).ConfigureAwait(false);
- }
-
- // Validate retrieved block
- if (!blockFromLocalNode.Check())
- {
- throw new InvalidOperationException("Disconnected node, because invalid block received!");
- }
-
- // Retrieved block from local node and block is valid
- Logger.LogInfo($"Block acquired from local P2P connection: {hash}.");
- return blockFromLocalNode;
- }
- catch (Exception ex)
- {
- DisconnectDisposeNullLocalBitcoinCoreNode();
-
- if (ex is SocketException)
- {
- Logger.LogTrace("Did not find local listening and running full node instance. Trying to fetch needed block from other source.");
- }
- else
- {
- Logger.LogWarning(ex);
- }
- }
- }
- else
- {
- try
- {
- var block = await CoreNode.RpcClient.GetBlockAsync(hash, cancellationToken).ConfigureAwait(false);
- Logger.LogInfo($"Block acquired from RPC connection: {hash}.");
- return block;
- }
- catch (Exception ex)
- {
- Logger.LogWarning(ex);
- }
- }
-
- return null;
- }
-
- private void DisconnectDisposeNullLocalBitcoinCoreNode()
- {
- if (LocalBitcoinCoreNode is { })
- {
- try
- {
- LocalBitcoinCoreNode?.Disconnect();
- }
- catch (Exception ex)
- {
- Logger.LogDebug(ex);
- }
- finally
- {
- try
- {
- LocalBitcoinCoreNode?.Dispose();
- }
- catch (Exception ex)
- {
- Logger.LogDebug(ex);
- }
- finally
- {
- LocalBitcoinCoreNode = null;
- Logger.LogInfo($"Local {Constants.BuiltinBitcoinNodeName} node disconnected.");
- }
- }
- }
- }
-
private void DisconnectNode(Node node, string logIfDisconnect, bool force = false)
{
if (Nodes.ConnectedNodes.Count > 3 || force)

MODIFY
WalletWasabi/Wallets/SmartBlockProvider.cs
WalletWasabi/Wallets/SmartBlockProvider.cs
@@ -10,7 +10,7 @@ namespace WalletWasabi.Wallets;
/// <see cref="SmartBlockProvider"/> is a block provider that can provide
/// blocks from multiple requesters.
/// </summary>
-public class SmartBlockProvider : IBlockProvider
+public class SmartBlockProvider
{
private static MemoryCacheEntryOptions CacheOptions = new()
{
@@ -18,24 +18,58 @@ public class SmartBlockProvider : IBlockProvider
SlidingExpiration = TimeSpan.FromSeconds(4)
};
- public SmartBlockProvider(IBlockProvider provider, IMemoryCache cache)
+ public SmartBlockProvider(CachedBlockProvider cachedProvider, LocalBlockProvider localBlockProvider, P2pBlockProvider p2PBlockProvider, IMemoryCache cache)
{
- InnerBlockProvider = provider;
+ CachedProvider = cachedProvider;
+ P2PBlockProvider = p2PBlockProvider;
+ LocalBlockProvider = localBlockProvider;
Cache = new(cache);
}
- private IBlockProvider InnerBlockProvider { get; }
+ private CachedBlockProvider CachedProvider { get; }
+ private LocalBlockProvider LocalBlockProvider { get; }
+ private P2pBlockProvider P2PBlockProvider { get; }
private IdempotencyRequestCache Cache { get; }
+ public IRepository<uint256, Block> BlockRepository => CachedProvider.BlockRepository;
+
public async Task<Block> GetBlockAsync(uint256 blockHash, CancellationToken cancel)
{
+ Block? result = await CachedProvider.TryGetBlockAsync(blockHash, cancel).ConfigureAwait(false);
+
+ if (result is not null)
+ {
+ return result;
+ }
+
string cacheKey = $"{nameof(SmartBlockProvider)}:{nameof(GetBlockAsync)}:{blockHash}";
- return await Cache.GetCachedResponseAsync(
+ result = await Cache.GetCachedResponseAsync(
cacheKey,
- action: (string request, CancellationToken token) => InnerBlockProvider.GetBlockAsync(blockHash, token),
+ action: (string request, CancellationToken token) => GetBlockNoCacheAsync(blockHash, token),
options: CacheOptions,
cancel).ConfigureAwait(false);
+
+ if (result is null)
+ {
+ throw new InvalidOperationException($"Block {blockHash} could not be downloaded from any source.");
}
+
+ await CachedProvider.SaveBlockAsync(result, cancel).ConfigureAwait(false);
+
+ return result;
+ }
+
+ private async Task<Block?> GetBlockNoCacheAsync(uint256 blockHash, CancellationToken cancel)
+ {
+ return await LocalBlockProvider.TryGetBlockAsync(blockHash, cancel).ConfigureAwait(false) ??
+ await P2PBlockProvider.TryGetBlockAsync(blockHash, cancel).ConfigureAwait(false);
+ }
+
+ public Task InvalidateAsync(uint256 hash, CancellationToken cancellationToken)
+ {
+ return CachedProvider.InvalidateAsync(hash, cancellationToken);
+ }
+
}

MODIFY
WalletWasabi/Wallets/Wallet.cs
WalletWasabi/Wallets/Wallet.cs
@@ -128,7 +128,7 @@ public class Wallet : BackgroundService, IWallet
public HybridFeeProvider FeeProvider { get; private set; }
public FilterModel LastProcessedFilter { get; private set; }
- public IBlockProvider BlockProvider { get; private set; }
+ public SmartBlockProvider BlockProvider { get; private set; }
private AsyncLock HandleFiltersLock { get; }
public bool IsLoggedIn { get; private set; }
@@ -166,7 +166,7 @@ public class Wallet : BackgroundService, IWallet
WasabiSynchronizer syncer,
ServiceConfiguration serviceConfiguration,
HybridFeeProvider feeProvider,
- IBlockProvider blockProvider)
+ SmartBlockProvider blockProvider)
{
if (State > WalletState.WaitingForInit)
{
@@ -366,10 +366,7 @@ public class Wallet : BackgroundService, IWallet
using (await HandleFiltersLock.LockAsync().ConfigureAwait(false))
{
uint256 invalidBlockHash = invalidFilter.Header.BlockHash;
- if (BlockProvider is CachedBlockProvider blockCache)
- {
- await blockCache.InvalidateAsync(invalidBlockHash, CancellationToken.None).ConfigureAwait(false);
- }
+ await BlockProvider.InvalidateAsync(invalidBlockHash, CancellationToken.None).ConfigureAwait(false);
KeyManager.SetMaxBestHeight(new Height(invalidFilter.Header.Height - 1));
TransactionProcessor.UndoBlock((int)invalidFilter.Header.Height);
@@ -519,7 +516,7 @@ public class Wallet : BackgroundService, IWallet
State = WalletState.WaitingForInit;
}
- public static Wallet CreateAndRegisterServices(Network network, BitcoinStore bitcoinStore, KeyManager keyManager, WasabiSynchronizer synchronizer, string dataDir, ServiceConfiguration serviceConfiguration, HybridFeeProvider feeProvider, IBlockProvider blockProvider)
+ public static Wallet CreateAndRegisterServices(Network network, BitcoinStore bitcoinStore, KeyManager keyManager, WasabiSynchronizer synchronizer, string dataDir, ServiceConfiguration serviceConfiguration, HybridFeeProvider feeProvider, SmartBlockProvider blockProvider)
{
var wallet = new Wallet(dataDir, network, keyManager);
wallet.RegisterServices(bitcoinStore, synchronizer, serviceConfiguration, feeProvider, blockProvider);

MODIFY
WalletWasabi/Wallets/WalletManager.cs
WalletWasabi/Wallets/WalletManager.cs
@@ -67,7 +67,7 @@ public class WalletManager : IWalletProvider
private HybridFeeProvider FeeProvider { get; set; }
public Network Network { get; }
public WalletDirectories WalletDirectories { get; }
- private IBlockProvider BlockProvider { get; set; }
+ private SmartBlockProvider BlockProvider { get; set; }
public string WorkDir { get; }
private void RefreshWalletList()
@@ -375,7 +375,7 @@ public class WalletManager : IWalletProvider
}
}
- public void RegisterServices(BitcoinStore bitcoinStore, WasabiSynchronizer synchronizer, ServiceConfiguration serviceConfiguration, HybridFeeProvider feeProvider, IBlockProvider blockProvider)
+ public void RegisterServices(BitcoinStore bitcoinStore, WasabiSynchronizer synchronizer, ServiceConfiguration serviceConfiguration, HybridFeeProvider feeProvider, SmartBlockProvider blockProvider)
{
BitcoinStore = bitcoinStore;
Synchronizer = synchronizer;

base refactor