Implement local Bitcoin Core block download from last blocks.
MODIFY
WalletWasabi.Gui/Config.cs
WalletWasabi.Gui/Config.cs
@@ -47,6 +47,24 @@ namespace WalletWasabi.Gui
[JsonProperty(PropertyName = "TorSocks5Port")]
public int? TorSocks5Port { get; internal set; }
+ [JsonProperty(PropertyName = "BitcoinCoreDataDir")]
+ public string BitcoinCoreDataDir
+ {
+ get => _bitcoinCoreDataDir;
+ internal set
+ {
+ if (_bitcoinCoreDataDir != value)
+ {
+ _bitcoinCoreDataDir = value;
+
+ if (!string.IsNullOrWhiteSpace(value) && ServiceConfiguration != default)
+ {
+ ServiceConfiguration.BitcoinCoreDataDir = value;
+ }
+ }
+ }
+ }
+
[JsonProperty(PropertyName = "MixUntilAnonymitySet")]
public int? MixUntilAnonymitySet
{
@@ -170,6 +188,7 @@ namespace WalletWasabi.Gui
private int? _privacyLevelSome;
private int? _privacyLevelFine;
private int? _privacyLevelStrong;
+ private string _bitcoinCoreDataDir;
public IPEndPoint GetTorSocks5EndPoint()
{
@@ -200,6 +219,7 @@ namespace WalletWasabi.Gui
string mainNetFallbackBackendUri,
string testNetFallbackBackendUri,
string regTestBackendUriV3,
+ string bitcoinCoreDataDir,
string torHost,
int? torSocks5Port,
int? mixUntilAnonymitySet,
@@ -219,12 +239,14 @@ namespace WalletWasabi.Gui
TorHost = Guard.NotNullOrEmptyOrWhitespace(nameof(torHost), torHost);
TorSocks5Port = Guard.NotNull(nameof(torSocks5Port), torSocks5Port);
+ BitcoinCoreDataDir = Guard.NotNullOrEmptyOrWhitespace(nameof(bitcoinCoreDataDir), bitcoinCoreDataDir);
+
MixUntilAnonymitySet = Guard.NotNull(nameof(mixUntilAnonymitySet), mixUntilAnonymitySet);
PrivacyLevelSome = Guard.NotNull(nameof(privacyLevelSome), privacyLevelSome);
PrivacyLevelFine = Guard.NotNull(nameof(privacyLevelFine), privacyLevelFine);
PrivacyLevelStrong = Guard.NotNull(nameof(privacyLevelStrong), privacyLevelStrong);
- ServiceConfiguration = new ServiceConfiguration(MixUntilAnonymitySet.Value, PrivacyLevelSome.Value, PrivacyLevelFine.Value, PrivacyLevelStrong.Value);
+ ServiceConfiguration = new ServiceConfiguration(MixUntilAnonymitySet.Value, PrivacyLevelSome.Value, PrivacyLevelFine.Value, PrivacyLevelStrong.Value, BitcoinCoreDataDir);
}
/// <inheritdoc />
@@ -250,6 +272,7 @@ namespace WalletWasabi.Gui
MainNetFallbackBackendUri = "https://wasabiwallet.io/";
TestNetFallbackBackendUri = "https://wasabiwallet.co/";
RegTestBackendUriV3 = "http://localhost:37127/";
+ BitcoinCoreDataDir = EnvironmentHelpers.TryGetDefaultBitcoinCoreDataDir() ?? "/bitcoin"; // whatever
TorHost = IPAddress.Loopback.ToString();
TorSocks5Port = 9050;
@@ -259,7 +282,7 @@ namespace WalletWasabi.Gui
PrivacyLevelFine = 21;
PrivacyLevelStrong = 50;
- ServiceConfiguration = new ServiceConfiguration(MixUntilAnonymitySet.Value, PrivacyLevelSome.Value, PrivacyLevelFine.Value, PrivacyLevelStrong.Value);
+ ServiceConfiguration = new ServiceConfiguration(MixUntilAnonymitySet.Value, PrivacyLevelSome.Value, PrivacyLevelFine.Value, PrivacyLevelStrong.Value, BitcoinCoreDataDir);
if (!File.Exists(FilePath))
{
@@ -288,6 +311,7 @@ namespace WalletWasabi.Gui
MainNetFallbackBackendUri = config.MainNetFallbackBackendUri ?? MainNetFallbackBackendUri;
TestNetFallbackBackendUri = config.TestNetFallbackBackendUri ?? TestNetFallbackBackendUri;
RegTestBackendUriV3 = config.RegTestBackendUriV3 ?? RegTestBackendUriV3;
+ BitcoinCoreDataDir = config.BitcoinCoreDataDir ?? BitcoinCoreDataDir;
TorHost = config.TorHost ?? TorHost;
TorSocks5Port = config.TorSocks5Port ?? TorSocks5Port;
@@ -351,6 +375,11 @@ namespace WalletWasabi.Gui
return true;
}
+ if (BitcoinCoreDataDir != config.BitcoinCoreDataDir) // case sensitive
+ {
+ return true;
+ }
+
if (MixUntilAnonymitySet != config.MixUntilAnonymitySet)
{
return true;

MODIFY
WalletWasabi/Helpers/EnvironmentHelpers.cs
WalletWasabi/Helpers/EnvironmentHelpers.cs
@@ -70,6 +70,55 @@ namespace WalletWasabi.Helpers
return directory;
}
+ public static string TryGetDefaultBitcoinCoreDataDir()
+ {
+ string directory = null;
+
+ // https://en.bitcoin.it/wiki/Data_directory
+ try
+ {
+ if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
+ {
+ var localAppData = Environment.GetEnvironmentVariable("APPDATA");
+ if (!string.IsNullOrEmpty(localAppData))
+ {
+ directory = Path.Combine(localAppData, "Bitcoin");
+ }
+ else
+ {
+ throw new DirectoryNotFoundException("Could not find suitable default Bitcoin Core datadir.");
+ }
+ }
+ else
+ {
+ var home = Environment.GetEnvironmentVariable("HOME");
+ if (!string.IsNullOrEmpty(home))
+ {
+ if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
+ {
+ directory = Path.Combine(home, "Library", "Application Support", "Bitcoin");
+ }
+ else // Linux
+ {
+ directory = Path.Combine(home, ".bitcoin");
+ }
+ }
+ else
+ {
+ throw new DirectoryNotFoundException("Could not find suitable default Bitcoin Core datadir.");
+ }
+ }
+
+ return directory;
+ }
+ catch (Exception ex)
+ {
+ Logger.LogDebug(ex, nameof(EnvironmentHelpers));
+ }
+
+ return directory;
+ }
+
/// <summary>
/// Executes a command with bash.
/// https://stackoverflow.com/a/47918132/2061103

MODIFY
WalletWasabi/Models/ServiceConfiguration.cs
WalletWasabi/Models/ServiceConfiguration.cs
@@ -11,17 +11,20 @@ namespace WalletWasabi.Models
public int PrivacyLevelSome { get; set; }
public int PrivacyLevelFine { get; set; }
public int PrivacyLevelStrong { get; set; }
+ public string BitcoinCoreDataDir { get; set; }
public ServiceConfiguration(
int mixUntilAnonymitySet,
int privacyLevelSome,
int privacyLevelFine,
- int privacyLevelStrong)
+ int privacyLevelStrong,
+ string bitcoinCoreDataDir)
{
MixUntilAnonymitySet = Guard.NotNull(nameof(mixUntilAnonymitySet), mixUntilAnonymitySet);
PrivacyLevelSome = Guard.NotNull(nameof(privacyLevelSome), privacyLevelSome);
PrivacyLevelFine = Guard.NotNull(nameof(privacyLevelFine), privacyLevelFine);
PrivacyLevelStrong = Guard.NotNull(nameof(privacyLevelStrong), privacyLevelStrong);
+ BitcoinCoreDataDir = Guard.NotNull(nameof(bitcoinCoreDataDir), bitcoinCoreDataDir);
}
}
}

MODIFY
WalletWasabi/Services/WalletService.cs
WalletWasabi/Services/WalletService.cs
@@ -21,6 +21,7 @@ using System.Collections.Concurrent;
using NBitcoin.DataEncoders;
using System.Net.Http;
using System.Diagnostics;
+using NBitcoin.BitcoinCore;
namespace WalletWasabi.Services
{
@@ -578,6 +579,12 @@ namespace WalletWasabi.Services
}
}
+ // Should be protected by BlockFolderLock
+ private static List<Block> LastBlocksFromCore { get; } = new List<Block>();
+
+ private static long LastBlkSize { get; set; } = 0;
+ private static string LastBlkName { get; set; } = "";
+
/// <exception cref="OperationCanceledException"></exception>
public async Task<Block> GetOrDownloadBlockAsync(uint256 hash, CancellationToken cancel)
{
@@ -612,6 +619,74 @@ namespace WalletWasabi.Services
cancel.ThrowIfCancellationRequested();
try
{
+ // Try to get block information from Bitcoin Core first.
+ try
+ {
+ string coreFolderPath = ServiceConfiguration?.BitcoinCoreDataDir;
+
+ // NBitcoin cannot parse testnet and regtest blocks
+ if (!string.IsNullOrWhiteSpace(coreFolderPath) && Network == Network.Main)
+ {
+ string blocksFolderPath = Path.Combine(coreFolderPath, "blocks");
+
+ // NBitcoin cannot parse testnet and regtest blocks
+ //string blocksFolderPath;
+ //if (Network == Network.Main)
+ //{
+ // blocksFolderPath = Path.Combine(coreFolderPath, "blocks");
+ //}
+ //else //if (Network == Network.TestNet)
+ //{
+ // blocksFolderPath = Path.Combine(coreFolderPath, "testnet3", "blocks");
+ //}
+ //else // if (Network == Network.RegTest)
+ //{
+ // blocksFolderPath = Path.Combine(coreFolderPath, "regtest", "blocks");
+ //}
+
+ if (Directory.Exists(blocksFolderPath))
+ {
+ var blocksFolder = new DirectoryInfo(blocksFolderPath);
+ var totalCount = 0;
+ foreach (var blkFile in blocksFolder
+ .EnumerateFiles("blk*.dat", new EnumerationOptions() { IgnoreInaccessible = true, RecurseSubdirectories = false })
+ .OrderByDescending(x => int.Parse(x.Name.Split(new[] { "blk", ".dat" }, StringSplitOptions.RemoveEmptyEntries).FirstOrDefault())))
+ {
+ // If nothing changed, then don't bother updating.
+ if (blkFile.Length == LastBlkSize && blkFile.Name == LastBlkName)
+ {
+ break;
+ }
+ LastBlkSize = blkFile.Length;
+ LastBlkName = blkFile.Name;
+ LastBlocksFromCore.Clear();
+
+ foreach (var storedBlock in StoredBlock.EnumerateFile(blkFile.FullName))
+ {
+ totalCount++;
+ LastBlocksFromCore.Add(storedBlock.Item);
+ }
+
+ if (totalCount > 100) // at least check the last 100 blocks (2sec with SSD)
+ {
+ break;
+ }
+ }
+ }
+ }
+ }
+ catch (Exception ex)
+ {
+ Logger.LogWarning<WalletService>(ex);
+ }
+
+ var foundBlockFromCore = LastBlocksFromCore.FirstOrDefault(x => x.Header.GetHash() == hash);
+ if (foundBlockFromCore != null && !foundBlockFromCore.HeaderOnly)
+ {
+ Logger.LogInfo<WalletService>($"Block acquired from disk (source: Bitcoin Core): {block.GetHash()}");
+ return foundBlockFromCore;
+ }
+
// If no connection, wait then continue.
while (Nodes.ConnectedNodes.Count == 0)
{

Implement local Bitcoin Core block download from last 100-200 blocks.