Abstract away the Bitcoin P2P Network from the Global
MODIFY
WalletWasabi.Fluent/ViewModels/MainViewModel.cs
WalletWasabi.Fluent/ViewModels/MainViewModel.cs
@@ -16,6 +16,7 @@ using WalletWasabi.Fluent.ViewModels.HelpAndSupport;
using WalletWasabi.Fluent.ViewModels.OpenDirectory;
using WalletWasabi.Services;
using WalletWasabi.Logging;
+using WalletWasabi.BitcoinP2p;
namespace WalletWasabi.Fluent.ViewModels
{
@@ -131,7 +132,7 @@ namespace WalletWasabi.Fluent.ViewModels
public void Initialize()
{
- StatusBar.Initialize(_global.Nodes.ConnectedNodes);
+ StatusBar.Initialize(_global.HostedServices.Get<P2pNetwork>().Nodes.ConnectedNodes);
if (Network != Network.Main)
{

MODIFY
WalletWasabi.Fluent/ViewModels/Settings/BitcoinTabSettingsViewModel.cs
WalletWasabi.Fluent/ViewModels/Settings/BitcoinTabSettingsViewModel.cs
@@ -40,7 +40,7 @@ namespace WalletWasabi.Fluent.ViewModels.Settings
_startLocalBitcoinCoreOnStartup = config.StartLocalBitcoinCoreOnStartup;
_localBitcoinCoreDataDir = config.LocalBitcoinCoreDataDir;
_stopLocalBitcoinCoreOnShutdown = config.StopLocalBitcoinCoreOnShutdown;
- _bitcoinP2PEndPoint = config.GetP2PEndpoint().ToString(defaultPort: -1);
+ _bitcoinP2PEndPoint = config.GetBitcoinP2pEndPoint().ToString(defaultPort: -1);
_dustThreshold = config.DustThreshold.ToString();
this.WhenAnyValue(
@@ -101,7 +101,7 @@ namespace WalletWasabi.Fluent.ViewModels.Settings
{
if (EndPointParser.TryParse(BitcoinP2PEndPoint, Network.DefaultPort, out EndPoint? p2PEp))
{
- config.SetP2PEndpoint(p2PEp);
+ config.SetBitcoinP2pEndpoint(p2PEp);
}
config.StartLocalBitcoinCoreOnStartup = StartLocalBitcoinCoreOnStartup;
@@ -114,7 +114,7 @@ namespace WalletWasabi.Fluent.ViewModels.Settings
else
{
config.Network = Network;
- BitcoinP2PEndPoint = config.GetP2PEndpoint().ToString(defaultPort: -1);
+ BitcoinP2PEndPoint = config.GetBitcoinP2pEndPoint().ToString(defaultPort: -1);
}
}
}

MODIFY
WalletWasabi.Gui/Config.cs
WalletWasabi.Gui/Config.cs
@@ -279,18 +279,7 @@ namespace WalletWasabi.Gui
}
}
- /// <inheritdoc />
- public override void LoadFile()
- {
- base.LoadFile();
-
- ServiceConfiguration = new ServiceConfiguration(MixUntilAnonymitySet, PrivacyLevelSome, PrivacyLevelFine, PrivacyLevelStrong, GetBitcoinP2pEndPoint(), DustThreshold);
-
- // Just debug convenience.
- _backendUri = GetCurrentBackendUri();
- }
-
- public void SetP2PEndpoint(EndPoint endPoint)
+ public void SetBitcoinP2pEndpoint(EndPoint endPoint)
{
if (Network == Network.Main)
{
@@ -310,24 +299,15 @@ namespace WalletWasabi.Gui
}
}
- public EndPoint GetP2PEndpoint()
- {
- if (Network == Network.Main)
- {
- return MainNetBitcoinP2pEndPoint;
- }
- else if (Network == Network.TestNet)
- {
- return TestNetBitcoinP2pEndPoint;
- }
- else if (Network == Network.RegTest)
- {
- return RegTestBitcoinP2pEndPoint;
- }
- else
+ /// <inheritdoc />
+ public override void LoadFile()
{
- throw new NotSupportedNetworkException(Network);
- }
+ base.LoadFile();
+
+ ServiceConfiguration = new ServiceConfiguration(MixUntilAnonymitySet, PrivacyLevelSome, PrivacyLevelFine, PrivacyLevelStrong, GetBitcoinP2pEndPoint(), DustThreshold);
+
+ // Just debug convenience.
+ _backendUri = GetCurrentBackendUri();
}
private int CorrectMixUntilAnonymitySetValue()

MODIFY
WalletWasabi.Gui/Global.cs
WalletWasabi.Gui/Global.cs
@@ -14,6 +14,7 @@ using System.Threading.Tasks;
using WalletWasabi.BitcoinCore;
using WalletWasabi.BitcoinCore.Endpointing;
using WalletWasabi.BitcoinCore.Monitoring;
+using WalletWasabi.BitcoinP2p;
using WalletWasabi.Blockchain.Analysis.FeesEstimation;
using WalletWasabi.Blockchain.BlockFilters;
using WalletWasabi.Blockchain.Blocks;
@@ -50,19 +51,12 @@ namespace WalletWasabi.Gui
public HttpClientFactory HttpClientFactory { get; }
public LegalChecker LegalChecker { get; private set; }
public Config Config { get; }
-
- public string AddressManagerFilePath { get; private set; }
- public AddressManager AddressManager { get; private set; }
-
- public NodesGroup Nodes { get; private set; }
public WasabiSynchronizer Synchronizer { get; private set; }
public WalletManager WalletManager { get; }
public TransactionBroadcaster TransactionBroadcaster { get; set; }
public CoinJoinProcessor CoinJoinProcessor { get; set; }
- public Node RegTestMempoolServingNode { get; private set; }
private TorProcessManager? TorManager { get; set; }
public CoreNode BitcoinCoreNode { get; private set; }
-
public HostedServices HostedServices { get; }
public UiConfig UiConfig { get; }
@@ -131,13 +125,6 @@ namespace WalletWasabi.Gui
cancel.ThrowIfCancellationRequested();
- var addressManagerFolderPath = Path.Combine(DataDir, "AddressManager");
- AddressManagerFilePath = Path.Combine(addressManagerFolderPath, $"AddressManager{Network}.dat");
- var addrManTask = InitializeAddressManagerBehaviorAsync();
-
- var userAgent = Constants.UserAgents.RandomElement();
- var connectionParameters = new NodeConnectionParameters { UserAgent = userAgent };
-
HostedServices.Register<UpdateChecker>(new UpdateChecker(TimeSpan.FromMinutes(7), Synchronizer), "Software Update Checker");
await LegalChecker.InitializeAsync(HostedServices.Get<UpdateChecker>()).ConfigureAwait(false);
@@ -184,6 +171,7 @@ namespace WalletWasabi.Gui
#endregion BitcoinStoreInitialization
+ HostedServices.Register<P2pNetwork>(new P2pNetwork(Network, Config.GetBitcoinP2pEndPoint(), Config.UseTor ? Config.TorSocks5EndPoint : null, Path.Combine(DataDir, "BitcoinP2pNetwork"), BitcoinStore), "Bitcoin P2P Network");
HostedServices.Register<FilterProcessor>(new FilterProcessor(Synchronizer, BitcoinStore), "Filter Processor");
cancel.ThrowIfCancellationRequested();
@@ -231,65 +219,6 @@ namespace WalletWasabi.Gui
cancel.ThrowIfCancellationRequested();
- #region MempoolInitialization
-
- connectionParameters.TemplateBehaviors.Add(BitcoinStore.CreateUntrustedP2pBehavior());
-
- #endregion MempoolInitialization
-
- cancel.ThrowIfCancellationRequested();
-
- #region AddressManagerInitialization
-
- AddressManagerBehavior addressManagerBehavior = await addrManTask.ConfigureAwait(false);
- connectionParameters.TemplateBehaviors.Add(addressManagerBehavior);
-
- #endregion AddressManagerInitialization
-
- cancel.ThrowIfCancellationRequested();
-
- #region P2PInitialization
-
- if (Network == Network.RegTest)
- {
- Nodes = new NodesGroup(Network, requirements: Constants.NodeRequirements);
- try
- {
- EndPoint bitcoinCoreEndpoint = Config.GetBitcoinP2pEndPoint();
-
- Node node = await Node.ConnectAsync(Network.RegTest, bitcoinCoreEndpoint).ConfigureAwait(false);
-
- Nodes.ConnectedNodes.Add(node);
-
- RegTestMempoolServingNode = await Node.ConnectAsync(Network.RegTest, bitcoinCoreEndpoint).ConfigureAwait(false);
-
- RegTestMempoolServingNode.Behaviors.Add(BitcoinStore.CreateUntrustedP2pBehavior());
- }
- catch (SocketException ex)
- {
- Logger.LogError(ex);
- }
- }
- else
- {
- Nodes = CreateAndConfigureNodesGroup(connectionParameters);
- RegTestMempoolServingNode = null;
- }
-
- Nodes.Connect();
- Logger.LogInfo("Start connecting to nodes...");
-
- var regTestMempoolServingNode = RegTestMempoolServingNode;
- if (regTestMempoolServingNode is { })
- {
- regTestMempoolServingNode.VersionHandshake();
- Logger.LogInfo("Start connecting to mempool serving regtest node...");
- }
-
- #endregion P2PInitialization
-
- cancel.ThrowIfCancellationRequested();
-
#region SynchronizerInitialization
var requestInterval = TimeSpan.FromSeconds(30);
@@ -307,7 +236,7 @@ namespace WalletWasabi.Gui
cancel.ThrowIfCancellationRequested();
- TransactionBroadcaster.Initialize(Nodes, BitcoinCoreNode?.RpcClient);
+ TransactionBroadcaster.Initialize(HostedServices.Get<P2pNetwork>().Nodes, BitcoinCoreNode?.RpcClient);
CoinJoinProcessor = new CoinJoinProcessor(Network, Synchronizer, WalletManager, BitcoinCoreNode?.RpcClient);
#region JsonRpcServerInitialization
@@ -335,7 +264,7 @@ namespace WalletWasabi.Gui
var blockProvider = new CachedBlockProvider(
new SmartBlockProvider(
- new P2pBlockProvider(Nodes, BitcoinCoreNode, HttpClientFactory, Config.ServiceConfiguration, Network),
+ new P2pBlockProvider(HostedServices.Get<P2pNetwork>().Nodes, BitcoinCoreNode, HttpClientFactory, Config.ServiceConfiguration, Network),
Cache),
BitcoinStore.BlockRepository);
@@ -351,97 +280,6 @@ namespace WalletWasabi.Gui
}
}
- private NodesGroup CreateAndConfigureNodesGroup(NodeConnectionParameters connectionParameters)
- {
- var maximumNodeConnection = 12;
- var bestEffortEndpointConnector = new BestEffortEndpointConnector(maximumNodeConnection / 2);
- connectionParameters.EndpointConnector = bestEffortEndpointConnector;
- if (Config.UseTor)
- {
- connectionParameters.TemplateBehaviors.Add(new SocksSettingsBehavior(Config.TorSocks5EndPoint, onlyForOnionHosts: false, networkCredential: null, streamIsolation: true));
- }
- var nodes = new NodesGroup(Network, connectionParameters, requirements: Constants.NodeRequirements);
- nodes.ConnectedNodes.Added += ConnectedNodes_OnAddedOrRemoved;
- nodes.ConnectedNodes.Removed += ConnectedNodes_OnAddedOrRemoved;
- nodes.MaximumNodeConnection = maximumNodeConnection;
- return nodes;
- }
-
- private async Task<AddressManagerBehavior> InitializeAddressManagerBehaviorAsync()
- {
- var needsToDiscoverPeers = true;
- if (Network == Network.RegTest)
- {
- AddressManager = new AddressManager();
- Logger.LogInfo($"Fake {nameof(AddressManager)} is initialized on the {Network.RegTest}.");
- }
- else
- {
- try
- {
- AddressManager = await NBitcoinHelpers.LoadAddressManagerFromPeerFileAsync(AddressManagerFilePath).ConfigureAwait(false);
-
- // Most of the times we do not need to discover new peers. Instead, we can connect to
- // some of those that we already discovered in the past. In this case we assume that
- // discovering new peers could be necessary if our address manager has less
- // than 500 addresses. 500 addresses could be okay because previously we tried with
- // 200 and only one user reported he/she was not able to connect (there could be many others,
- // of course).
- // On the other side, increasing this number forces users that do not need to discover more peers
- // to spend resources (CPU/bandwidth) to discover new peers.
- needsToDiscoverPeers = Config.UseTor || AddressManager.Count < 500;
- Logger.LogInfo($"Loaded {nameof(AddressManager)} from `{AddressManagerFilePath}`.");
- }
- catch (DirectoryNotFoundException ex)
- {
- Logger.LogInfo($"{nameof(AddressManager)} did not exist at `{AddressManagerFilePath}`. Initializing new one.");
- Logger.LogTrace(ex);
- AddressManager = new AddressManager();
- }
- catch (FileNotFoundException ex)
- {
- Logger.LogInfo($"{nameof(AddressManager)} did not exist at `{AddressManagerFilePath}`. Initializing new one.");
- Logger.LogTrace(ex);
- AddressManager = new AddressManager();
- }
- catch (OverflowException ex)
- {
- // https://github.com/zkSNACKs/WalletWasabi/issues/712
- Logger.LogInfo($"{nameof(AddressManager)} has thrown `{nameof(OverflowException)}`. Attempting to autocorrect.");
- File.Delete(AddressManagerFilePath);
- Logger.LogTrace(ex);
- AddressManager = new AddressManager();
- Logger.LogInfo($"{nameof(AddressManager)} autocorrection is successful.");
- }
- catch (FormatException ex)
- {
- // https://github.com/zkSNACKs/WalletWasabi/issues/880
- Logger.LogInfo($"{nameof(AddressManager)} has thrown `{nameof(FormatException)}`. Attempting to autocorrect.");
- File.Delete(AddressManagerFilePath);
- Logger.LogTrace(ex);
- AddressManager = new AddressManager();
- Logger.LogInfo($"{nameof(AddressManager)} autocorrection is successful.");
- }
- }
-
- var addressManagerBehavior = new AddressManagerBehavior(AddressManager)
- {
- Mode = needsToDiscoverPeers ? AddressManagerBehaviorMode.Discover : AddressManagerBehaviorMode.None
- };
- return addressManagerBehavior;
- }
-
- private void ConnectedNodes_OnAddedOrRemoved(object? sender, NodeEventArgs e)
- {
- if (Nodes.NodeConnectionParameters.EndpointConnector is BestEffortEndpointConnector bestEffortEndPointConnector)
- {
- if (sender is NodesCollection nodesCollection)
- {
- bestEffortEndPointConnector.UpdateConnectedNodesCounter(nodesCollection.Count);
- }
- }
- }
-
private void WalletManager_OnDequeue(object? sender, DequeueResult e)
{
try
@@ -666,42 +504,6 @@ namespace WalletWasabi.Gui
Logger.LogInfo($"{nameof(Synchronizer)} is stopped.");
}
- Logger.LogDebug($"Step: {nameof(AddressManagerFilePath)}.", nameof(Global));
-
- if (AddressManagerFilePath is { } addressManagerFilePath)
- {
- IoHelpers.EnsureContainingDirectoryExists(addressManagerFilePath);
- var addressManager = AddressManager;
- if (addressManager is { })
- {
- addressManager.SavePeerFile(AddressManagerFilePath, Config.Network);
- Logger.LogInfo($"{nameof(AddressManager)} is saved to `{AddressManagerFilePath}`.");
- }
- }
-
- Logger.LogDebug($"Step: {nameof(Nodes)}.", nameof(Global));
-
- if (Nodes is { } nodes)
- {
- Nodes.ConnectedNodes.Added -= ConnectedNodes_OnAddedOrRemoved;
- Nodes.ConnectedNodes.Removed -= ConnectedNodes_OnAddedOrRemoved;
- nodes.Disconnect();
- while (nodes.ConnectedNodes.Any(x => x.IsConnected))
- {
- await Task.Delay(50).ConfigureAwait(false);
- }
- nodes.Dispose();
- Logger.LogInfo($"{nameof(Nodes)} are disposed.");
- }
-
- Logger.LogDebug($"Step: {nameof(RegTestMempoolServingNode)}.", nameof(Global));
-
- if (RegTestMempoolServingNode is { } regTestMempoolServingNode)
- {
- regTestMempoolServingNode.Disconnect();
- Logger.LogInfo($"{nameof(RegTestMempoolServingNode)} is disposed.");
- }
-
Logger.LogDebug($"Step: {nameof(BitcoinCoreNode)}.", nameof(Global));
if (BitcoinCoreNode is { } bitcoinCoreNode)

MODIFY
WalletWasabi.Gui/Program.cs
WalletWasabi.Gui/Program.cs
@@ -11,6 +11,7 @@ using System.IO;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
+using WalletWasabi.BitcoinP2p;
using WalletWasabi.Gui.ViewModels;
using WalletWasabi.Helpers;
using WalletWasabi.Logging;
@@ -85,7 +86,7 @@ namespace WalletWasabi.Gui
await Global.InitializeNoWalletAsync(TerminateService);
- MainWindowViewModel.Instance.Initialize(Global.Nodes.ConnectedNodes);
+ MainWindowViewModel.Instance.Initialize(Global.HostedServices.Get<P2pNetwork>().Nodes.ConnectedNodes);
Dispatcher.UIThread.Post(GC.Collect);
}

MODIFY
WalletWasabi.Gui/Rpc/WasabiJsonRpcService.cs
WalletWasabi.Gui/Rpc/WasabiJsonRpcService.cs
@@ -2,6 +2,7 @@ using System;
using System.Linq;
using System.Threading.Tasks;
using NBitcoin;
+using WalletWasabi.BitcoinP2p;
using WalletWasabi.Blockchain.Analysis.Clustering;
using WalletWasabi.Blockchain.Keys;
using WalletWasabi.Blockchain.TransactionBuilding;
@@ -113,7 +114,7 @@ namespace WalletWasabi.Gui.Rpc
filtersLeft = sync.BitcoinStore.SmartHeaderChain.HashesLeft,
network = Global.Network.Name,
exchangeRate = sync.UsdExchangeRate,
- peers = Global.Nodes.ConnectedNodes.Select(x => new
+ peers = Global.HostedServices.Get<P2pNetwork>().Nodes.ConnectedNodes.Select(x => new
{
isConnected = x.IsConnected,
lastSeen = x.LastSeen,

MODIFY
WalletWasabi.Gui/Tabs/SettingsViewModel.cs
WalletWasabi.Gui/Tabs/SettingsViewModel.cs
@@ -75,7 +75,7 @@ namespace WalletWasabi.Gui.Tabs
_dustThreshold = config.DustThreshold.ToString();
- _bitcoinP2pEndPoint = config.GetP2PEndpoint().ToString(defaultPort: -1);
+ _bitcoinP2pEndPoint = config.GetBitcoinP2pEndPoint().ToString(defaultPort: -1);
_localBitcoinCoreDataDir = config.LocalBitcoinCoreDataDir;
IsModified = !Global.Config.AreDeepEqual(config);
@@ -363,7 +363,7 @@ namespace WalletWasabi.Gui.Tabs
}
if (EndPointParser.TryParse(BitcoinP2pEndPoint, network.DefaultPort, out EndPoint? p2pEp))
{
- config.SetP2PEndpoint(p2pEp);
+ config.SetBitcoinP2pEndpoint(p2pEp);
}
config.UseTor = UseTor;
config.TerminateTorOnExit = TerminateTorOnExit;
@@ -378,7 +378,7 @@ namespace WalletWasabi.Gui.Tabs
else
{
config.Network = Network;
- BitcoinP2pEndPoint = config.GetP2PEndpoint().ToString(defaultPort: -1);
+ BitcoinP2pEndPoint = config.GetBitcoinP2pEndPoint().ToString(defaultPort: -1);
}
config.ToFile();
IsModified = !Global.Config.AreDeepEqual(config);

ADD
WalletWasabi/BitcoinP2p/P2pNetwork.cs
+using Microsoft.Extensions.Hosting;
+using NBitcoin;
+using NBitcoin.Protocol;
+using NBitcoin.Protocol.Behaviors;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Net;
+using System.Net.Sockets;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+using WalletWasabi.Helpers;
+using WalletWasabi.Logging;
+using WalletWasabi.Stores;
+
+namespace WalletWasabi.BitcoinP2p
+{
+ public class P2pNetwork : BackgroundService
+ {
+ public P2pNetwork(Network network, EndPoint fullnodeP2pEndPoint, EndPoint? torSocks5EndPoint, string workDir, BitcoinStore bitcoinStore)
+ {
+ Network = network;
+ FullnodeP2PEndPoint = fullnodeP2pEndPoint;
+ TorSocks5EndPoint = torSocks5EndPoint;
+ WorkDir = workDir;
+ BitcoinStore = bitcoinStore;
+
+ var userAgent = Constants.UserAgents.RandomElement();
+ var connectionParameters = new NodeConnectionParameters { UserAgent = userAgent };
+
+ connectionParameters.TemplateBehaviors.Add(BitcoinStore.CreateUntrustedP2pBehavior());
+
+ AddressManagerFilePath = Path.Combine(WorkDir, $"AddressManager{Network}.dat");
+ var needsToDiscoverPeers = true;
+ if (Network == Network.RegTest)
+ {
+ AddressManager = new AddressManager();
+ Logger.LogInfo($"Fake {nameof(AddressManager)} is initialized on the {Network.RegTest}.");
+ }
+ else
+ {
+ try
+ {
+ AddressManager = AddressManager.LoadPeerFile(AddressManagerFilePath);
+
+ // Most of the times we do not need to discover new peers. Instead, we can connect to
+ // some of those that we already discovered in the past. In this case we assume that
+ // discovering new peers could be necessary if our address manager has less
+ // than 500 addresses. 500 addresses could be okay because previously we tried with
+ // 200 and only one user reported he/she was not able to connect (there could be many others,
+ // of course).
+ // On the other side, increasing this number forces users that do not need to discover more peers
+ // to spend resources (CPU/bandwidth) to discover new peers.
+ needsToDiscoverPeers = TorSocks5EndPoint is not null || AddressManager.Count < 500;
+ Logger.LogInfo($"Loaded {nameof(AddressManager)} from `{AddressManagerFilePath}`.");
+ }
+ catch (DirectoryNotFoundException ex)
+ {
+ Logger.LogInfo($"{nameof(AddressManager)} did not exist at `{AddressManagerFilePath}`. Initializing new one.");
+ Logger.LogTrace(ex);
+ AddressManager = new AddressManager();
+ }
+ catch (FileNotFoundException ex)
+ {
+ Logger.LogInfo($"{nameof(AddressManager)} did not exist at `{AddressManagerFilePath}`. Initializing new one.");
+ Logger.LogTrace(ex);
+ AddressManager = new AddressManager();
+ }
+ catch (OverflowException ex)
+ {
+ // https://github.com/zkSNACKs/WalletWasabi/issues/712
+ Logger.LogInfo($"{nameof(AddressManager)} has thrown `{nameof(OverflowException)}`. Attempting to autocorrect.");
+ File.Delete(AddressManagerFilePath);
+ Logger.LogTrace(ex);
+ AddressManager = new AddressManager();
+ Logger.LogInfo($"{nameof(AddressManager)} autocorrection is successful.");
+ }
+ catch (FormatException ex)
+ {
+ // https://github.com/zkSNACKs/WalletWasabi/issues/880
+ Logger.LogInfo($"{nameof(AddressManager)} has thrown `{nameof(FormatException)}`. Attempting to autocorrect.");
+ File.Delete(AddressManagerFilePath);
+ Logger.LogTrace(ex);
+ AddressManager = new AddressManager();
+ Logger.LogInfo($"{nameof(AddressManager)} autocorrection is successful.");
+ }
+ }
+
+ var addressManagerBehavior = new AddressManagerBehavior(AddressManager)
+ {
+ Mode = needsToDiscoverPeers ? AddressManagerBehaviorMode.Discover : AddressManagerBehaviorMode.None
+ };
+
+ connectionParameters.TemplateBehaviors.Add(addressManagerBehavior);
+
+ if (Network == Network.RegTest)
+ {
+ Nodes = new NodesGroup(Network, requirements: Constants.NodeRequirements);
+ }
+ else
+ {
+ var maximumNodeConnection = 12;
+ var bestEffortEndpointConnector = new BestEffortEndpointConnector(maximumNodeConnection / 2);
+ connectionParameters.EndpointConnector = bestEffortEndpointConnector;
+ if (TorSocks5EndPoint is not null)
+ {
+ connectionParameters.TemplateBehaviors.Add(new SocksSettingsBehavior(TorSocks5EndPoint, onlyForOnionHosts: false, networkCredential: null, streamIsolation: true));
+ }
+ var nodes = new NodesGroup(Network, connectionParameters, requirements: Constants.NodeRequirements);
+ nodes.ConnectedNodes.Added += ConnectedNodes_OnAddedOrRemoved;
+ nodes.ConnectedNodes.Removed += ConnectedNodes_OnAddedOrRemoved;
+ nodes.MaximumNodeConnection = maximumNodeConnection;
+
+ Nodes = nodes;
+ }
+ }
+
+ public Network Network { get; }
+ public EndPoint FullnodeP2PEndPoint { get; }
+ public EndPoint? TorSocks5EndPoint { get; }
+ public string WorkDir { get; }
+ public BitcoinStore BitcoinStore { get; }
+ public NodesGroup Nodes { get; }
+ private Node? RegTestMempoolServingNode { get; set; }
+ private string? AddressManagerFilePath { get; set; }
+ private AddressManager? AddressManager { get; set; }
+
+ /// <inheritdoc />
+ protected override async Task ExecuteAsync(CancellationToken stoppingToken)
+ {
+ if (Network == Network.RegTest)
+ {
+ try
+ {
+ EndPoint bitcoinCoreEndpoint = FullnodeP2PEndPoint;
+
+ Node node = await Node.ConnectAsync(Network.RegTest, bitcoinCoreEndpoint).ConfigureAwait(false);
+
+ Nodes.ConnectedNodes.Add(node);
+
+ RegTestMempoolServingNode = await Node.ConnectAsync(Network.RegTest, bitcoinCoreEndpoint).ConfigureAwait(false);
+
+ RegTestMempoolServingNode.Behaviors.Add(BitcoinStore.CreateUntrustedP2pBehavior());
+ }
+ catch (SocketException ex)
+ {
+ Logger.LogError(ex);
+ }
+ }
+
+ Nodes.Connect();
+
+ var regTestMempoolServingNode = RegTestMempoolServingNode;
+ if (regTestMempoolServingNode is not null)
+ {
+ regTestMempoolServingNode.VersionHandshake(stoppingToken);
+ Logger.LogInfo("Start connecting to mempool serving regtest node...");
+ }
+ }
+
+ private void ConnectedNodes_OnAddedOrRemoved(object? sender, NodeEventArgs e)
+ {
+ var nodes = Nodes;
+ if (nodes is not null && sender is NodesCollection nodesCollection && nodes.NodeConnectionParameters.EndpointConnector is BestEffortEndpointConnector bestEffortEndPointConnector)
+ {
+ bestEffortEndPointConnector.UpdateConnectedNodesCounter(nodesCollection.Count);
+ }
+ }
+
+ public override async Task StopAsync(CancellationToken cancellationToken)
+ {
+ if (AddressManagerFilePath is { } addressManagerFilePath)
+ {
+ IoHelpers.EnsureContainingDirectoryExists(addressManagerFilePath);
+ var addressManager = AddressManager;
+ if (addressManager is { })
+ {
+ addressManager.SavePeerFile(AddressManagerFilePath, Network);
+ Logger.LogInfo($"{nameof(AddressManager)} is saved to `{AddressManagerFilePath}`.");
+ }
+ }
+
+ cancellationToken.ThrowIfCancellationRequested();
+
+ Nodes.Disconnect();
+ while (Nodes.ConnectedNodes.Any(x => x.IsConnected))
+ {
+ await Task.Delay(50, cancellationToken).ConfigureAwait(false);
+ }
+
+ cancellationToken.ThrowIfCancellationRequested();
+
+ if (RegTestMempoolServingNode is { } regTestMempoolServingNode)
+ {
+ regTestMempoolServingNode.Disconnect();
+ Logger.LogInfo($"{nameof(RegTestMempoolServingNode)} is disposed.");
+ }
+
+ await base.StopAsync(cancellationToken).ConfigureAwait(false);
+ }
+
+ public override void Dispose()
+ {
+ Nodes.ConnectedNodes.Added -= ConnectedNodes_OnAddedOrRemoved;
+ Nodes.ConnectedNodes.Removed -= ConnectedNodes_OnAddedOrRemoved;
+ Nodes.Dispose();
+ base.Dispose();
+ }
+ }
+}

Abstract away the Bitcoin P2P Network from the Global