GetOrDownloadBlock refactor
MODIFY
WalletWasabi.Tests/P2pTests.cs
WalletWasabi.Tests/P2pTests.cs
@@ -131,7 +131,7 @@ namespace WalletWasabi.Tests
{
using (var cts = new CancellationTokenSource(TimeSpan.FromMinutes(3)))
{
- var block = await walletService.GetOrDownloadBlockAsync(hash, cts.Token);
+ var block = await walletService.FetchBlockAsync(hash, cts.Token);
Assert.True(File.Exists(Path.Combine(blocksFolderPath, hash.ToString())));
Logger.LogInfo<P2pTests>($"Full block is downloaded: {hash}.");
}

MODIFY
WalletWasabi/Services/WalletService.cs
WalletWasabi/Services/WalletService.cs
@@ -328,7 +328,7 @@ namespace WalletWasabi.Services
foreach (BlockState blockState in KeyManager.GetTransactionIndex())
{
var relevantTransactions = confirmedTransactions.Where(x => x.BlockHash == blockState.BlockHash).ToArray();
- var block = await GetOrDownloadBlockAsync(blockState.BlockHash, cancel);
+ var block = await FetchBlockAsync(blockState.BlockHash, cancel);
await ProcessBlockAsync(blockState.BlockHeight, block, blockState.TransactionIndices, relevantTransactions);
}
@@ -415,7 +415,7 @@ namespace WalletWasabi.Services
return;
}
- Block currentBlock = await GetOrDownloadBlockAsync(filterModel.BlockHash, cancel); // Wait until not downloaded.
+ Block currentBlock = await FetchBlockAsync(filterModel.BlockHash, cancel); // Wait until not downloaded.
if (await ProcessBlockAsync(filterModel.BlockHeight, currentBlock))
{
@@ -586,41 +586,52 @@ namespace WalletWasabi.Services
private set => _localBitcoinCoreNode = value;
}
+ /// <param name="hash">Block hash of the desired block, represented as a 256 bit integer.</param>
/// <exception cref="OperationCanceledException"></exception>
- public async Task<Block> GetOrDownloadBlockAsync(uint256 hash, CancellationToken cancel)
+ public async Task<Block> FetchBlockAsync(uint256 hash, CancellationToken cancel)
+ {
+ Block block = await GetBlockFromFileAsync(hash, cancel);
+ if (block == null)
+ {
+ block = await DownloadBlockAsync(hash, cancel);
+ }
+ return block;
+ }
+
+ /// <param name="hash">Block hash of the desired block, represented as a 256 bit integer.</param>
+ /// <exception cref="OperationCanceledException"></exception>
+ private async Task<Block> GetBlockFromFileAsync(uint256 hash, CancellationToken cancel)
{
// Try get the block
+ Block block = null;
using (await BlockFolderLock.LockAsync())
{
var encoder = new HexEncoder();
- foreach (var filePath in Directory.EnumerateFiles(BlocksFolderPath))
- {
- var fileName = Path.GetFileName(filePath);
- if (!encoder.IsValid(fileName))
+ var filePath = Path.Combine(BlocksFolderPath, encoder.EncodeData(hash.ToBytes()));
+ if (Directory.Exists(filePath))
{
- Logger.LogTrace<WalletService>($"Filename is not a hash: {fileName}.");
- continue;
- }
-
- if (hash == new uint256(fileName))
- {
- var blockBytes = await File.ReadAllBytesAsync(filePath);
try
{
- return Block.Load(blockBytes, Synchronizer.Network);
+ var blockBytes = await File.ReadAllBytesAsync(filePath);
+ block = Block.Load(blockBytes, Synchronizer.Network);
}
catch (Exception)
{
- // In case the block file is corrupted we get an EndOfStreamException exception
- // Ignore any error and continue by re-downloading the block.
- break;
- }
+ // In case the block file is corrupted and we get an EndOfStreamException exception
+ // Ignore any error and continue to re-downloading the block.
}
}
}
+
cancel.ThrowIfCancellationRequested();
- // Download the block
+ return block;
+ }
+
+ /// <param name="hash">Block hash of the desired block, represented as a 256 bit integer.</param>
+ /// <exception cref="OperationCanceledException"></exception>
+ private async Task<Block> DownloadBlockAsync(uint256 hash, CancellationToken cancel)
+ {
Block block = null;
try
{
@@ -633,6 +644,96 @@ namespace WalletWasabi.Services
try
{
// Try to get block information from local running Core node first.
+ block = await DownloadBlockFromLocalNodeAsync(hash, cancel);
+
+ if (block != null)
+ {
+ break;
+ }
+
+ // If no connection, wait, then continue.
+ while (Nodes.ConnectedNodes.Count == 0)
+ {
+ await Task.Delay(100);
+ }
+
+ // Select a random node we are connected to.
+ Node node = Nodes.ConnectedNodes.RandomElement();
+ if (node == default(Node) && !node.IsConnected && !(Synchronizer.Network != Network.RegTest))
+ {
+ await Task.Delay(100);
+ continue;
+ }
+
+ // Download block from selected node.
+ try
+ {
+ using (var cts = new CancellationTokenSource(TimeSpan.FromSeconds(RuntimeParams.Instance.NetworkNodeTimeout))) // 1/2 ADSL 512 kbit/s 00:00:32
+ {
+ block = await node.DownloadBlockAsync(hash, cts.Token);
+ }
+
+ // Validate block
+ if (!block.Check())
+ {
+ Logger.LogInfo<WalletService>($"Disconnected node: {node.RemoteSocketAddress}, because invalid block received.");
+ node.DisconnectAsync("Invalid block received.");
+ continue;
+ }
+
+ if (Nodes.ConnectedNodes.Count > 1) // To minimize risking missing unconfirmed transactions.
+ {
+ Logger.LogInfo<WalletService>($"Disconnected node: {node.RemoteSocketAddress}. Block downloaded: {block.GetHash()}");
+ node.DisconnectAsync("Thank you!");
+ }
+
+ await NodeTimeoutsAsync(false);
+ }
+ catch (Exception ex) when (ex is OperationCanceledException
+ || ex is TaskCanceledException
+ || ex is TimeoutException)
+ {
+ Logger.LogInfo<WalletService>($"Disconnected node: {node.RemoteSocketAddress}, because block download took too long.");
+
+ await NodeTimeoutsAsync(true);
+
+ node.DisconnectAsync("Block download took too long.");
+ continue;
+ }
+ catch (Exception ex)
+ {
+ Logger.LogDebug<WalletService>(ex);
+ Logger.LogInfo<WalletService>($"Disconnected node: {node.RemoteSocketAddress}, because block download failed: {ex.Message}");
+ node.DisconnectAsync("Block download failed.");
+ continue;
+ }
+
+ break; // If got this far, then we have the block and it's valid. Break.
+ }
+ catch (Exception ex)
+ {
+ Logger.LogDebug<WalletService>(ex);
+ }
+ }
+
+ // Save the block
+ using (await BlockFolderLock.LockAsync())
+ {
+ var path = Path.Combine(BlocksFolderPath, hash.ToString());
+ await File.WriteAllBytesAsync(path, block.ToBytes());
+ }
+ }
+ finally
+ {
+ DownloadingBlock = false;
+ BlockDownloadLock.ReleaseLock();
+ }
+
+ return block;
+ }
+
+ private async Task<Block> DownloadBlockFromLocalNodeAsync(uint256 hash, CancellationToken cancel)
+ {
try
{
if (LocalBitcoinCoreNode is null || !LocalBitcoinCoreNode.IsConnected && Network != Network.RegTest) // If RegTest then we're already connected do not try again.
@@ -659,7 +760,7 @@ namespace WalletWasabi.Services
var localNode = await Node.ConnectAsync(Network, localEndPoint, nodeConnectionParameters);
try
{
- Logger.LogInfo<WalletService>($"TCP Connection succeeded, handshaking...");
+ Logger.LogInfo<WalletService>("TCP Connection succeeded, handshaking...");
localNode.VersionHandshake(Constants.LocalNodeRequirements, handshakeTimeout.Token);
var peerServices = localNode.PeerVersion.Services;
@@ -686,6 +787,7 @@ namespace WalletWasabi.Services
}
}
+ // Get Block from local node
Block blockFromLocalNode = null;
// 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)))
@@ -693,18 +795,18 @@ namespace WalletWasabi.Services
blockFromLocalNode = await LocalBitcoinCoreNode.DownloadBlockAsync(hash, cts.Token);
}
+ // Validate retrieved block
if (!blockFromLocalNode.Check())
{
- throw new InvalidOperationException($"Disconnected node, because invalid block received!");
+ throw new InvalidOperationException("Disconnected node, because invalid block received!");
}
- block = blockFromLocalNode;
+ // Retrieved block from local node and block is valid
Logger.LogInfo<WalletService>($"Block acquired from local P2P connection: {hash}");
- break;
+ return blockFromLocalNode;
}
catch (Exception ex)
{
- block = null;
DisconnectDisposeNullLocalBitcoinCoreNode();
if (ex is SocketException)
@@ -718,88 +820,7 @@ namespace WalletWasabi.Services
}
cancel.ThrowIfCancellationRequested();
- // If no connection, wait then continue.
- while (Nodes.ConnectedNodes.Count == 0)
- {
- await Task.Delay(100);
- }
-
- Node node = Nodes.ConnectedNodes.RandomElement();
- if (node == default(Node))
- {
- await Task.Delay(100);
- continue;
- }
-
- if (!node.IsConnected && !(Synchronizer.Network != Network.RegTest))
- {
- await Task.Delay(100);
- continue;
- }
-
- try
- {
- using (var cts = new CancellationTokenSource(TimeSpan.FromSeconds(RuntimeParams.Instance.NetworkNodeTimeout))) // 1/2 ADSL 512 kbit/s 00:00:32
- {
- block = await node.DownloadBlockAsync(hash, cts.Token);
- }
-
- if (!block.Check())
- {
- Logger.LogInfo<WalletService>($"Disconnected node: {node.RemoteSocketAddress}, because invalid block received.");
- node.DisconnectAsync("Invalid block received.");
- continue;
- }
-
- if (Nodes.ConnectedNodes.Count > 1) // So to minimize risking missing unconfirmed transactions.
- {
- Logger.LogInfo<WalletService>($"Disconnected node: {node.RemoteSocketAddress}. Block downloaded: {block.GetHash()}");
- node.DisconnectAsync("Thank you!");
- }
-
- await NodeTimeoutsAsync(false);
- }
- catch (Exception ex) when (ex is OperationCanceledException
- || ex is TaskCanceledException
- || ex is TimeoutException)
- {
- Logger.LogInfo<WalletService>($"Disconnected node: {node.RemoteSocketAddress}, because block download took too long.");
-
- await NodeTimeoutsAsync(true);
-
- node.DisconnectAsync("Block download took too long.");
- continue;
- }
- catch (Exception ex)
- {
- Logger.LogDebug<WalletService>(ex);
- Logger.LogInfo<WalletService>($"Disconnected node: {node.RemoteSocketAddress}, because block download failed: {ex.Message}");
- node.DisconnectAsync("Block download failed.");
- continue;
- }
-
- break; // If got this far break, then we have the block, it's valid. Break.
- }
- catch (Exception ex)
- {
- Logger.LogDebug<WalletService>(ex);
- }
- }
-
- // Save the block
- using (await BlockFolderLock.LockAsync())
- {
- var path = Path.Combine(BlocksFolderPath, hash.ToString());
- await File.WriteAllBytesAsync(path, block.ToBytes());
- }
- }
- finally
- {
- DownloadingBlock = false;
- BlockDownloadLock.ReleaseLock();
- }
-
- return block;
+ return null;
}
private void DisconnectDisposeNullLocalBitcoinCoreNode()

GetOrDownloadBlock refactor