Create P2pNode
MODIFY
WalletWasabi.Backend/Global.cs
WalletWasabi.Backend/Global.cs
@@ -6,6 +6,7 @@ using System.IO;
using System.Net;
using System.Threading;
using System.Threading.Tasks;
+using WalletWasabi.BitcoinCore;
using WalletWasabi.Blockchain.BlockFilters;
using WalletWasabi.Blockchain.Blocks;
using WalletWasabi.Blockchain.Mempool;
@@ -24,7 +25,7 @@ namespace WalletWasabi.Backend
public RPCClient RpcClient { get; private set; }
- public Node LocalNode { get; private set; }
+ public P2pNode P2pNode { get; private set; }
public BlockNotifier BlockNotifier { get; private set; }
@@ -47,7 +48,7 @@ namespace WalletWasabi.Backend
{
}
- public async Task InitializeAsync(Config config, CoordinatorRoundConfig roundConfig, RPCClient rpc)
+ public async Task InitializeAsync(Config config, CoordinatorRoundConfig roundConfig, RPCClient rpc, CancellationToken cancel)
{
Config = Guard.NotNull(nameof(config), config);
RoundConfig = Guard.NotNull(nameof(roundConfig), roundConfig);
@@ -57,7 +58,7 @@ namespace WalletWasabi.Backend
await AssertRpcNodeFullyInitializedAsync();
// Make sure P2P works.
- await InitializeP2pAsync(config.Network, config.GetBitcoinP2pEndPoint());
+ await InitializeP2pAsync(config.Network, config.GetBitcoinP2pEndPoint(), cancel);
// Initialize index building
var indexBuilderServiceDir = Path.Combine(DataDir, "IndexBuilderService");
@@ -94,83 +95,16 @@ namespace WalletWasabi.Backend
}
}
- public void DisconnectDisposeNullLocalNode()
- {
- if (LocalNode != null)
- {
- try
- {
- LocalNode?.Disconnect();
- }
- catch (Exception ex)
- {
- Logger.LogDebug(ex);
- }
- finally
- {
- try
- {
- LocalNode?.Dispose();
- }
- catch (Exception ex)
- {
- Logger.LogDebug(ex);
- }
- finally
- {
- LocalNode = null;
- Logger.LogInfo("Local Bitcoin node is disconnected.");
- }
- }
- }
- }
-
- private async Task InitializeP2pAsync(Network network, EndPoint endPoint)
+ private async Task InitializeP2pAsync(Network network, EndPoint endPoint, CancellationToken cancel)
{
Guard.NotNull(nameof(network), network);
Guard.NotNull(nameof(endPoint), endPoint);
- using var handshakeTimeout = new CancellationTokenSource();
- handshakeTimeout.CancelAfter(TimeSpan.FromSeconds(10));
- var nodeConnectionParameters = new NodeConnectionParameters()
- {
- UserAgent = $"/WasabiCoordinator:{Constants.BackendMajorVersion.ToString()}/",
- ConnectCancellation = handshakeTimeout.Token,
- IsRelay = true
- };
-
- nodeConnectionParameters.TemplateBehaviors.Add(new TrustedP2pBehavior(new MempoolService()));
- var node = await Node.ConnectAsync(network, endPoint, nodeConnectionParameters);
// We have to find it, because it's cloned by the node and not perfectly cloned (event handlers cannot be cloned.)
- BlockNotifier = new BlockNotifier(TimeSpan.FromSeconds(7), RpcClient, node.Behaviors.Find<TrustedP2pBehavior>());
+ var p2pNode = new P2pNode(network, endPoint, new MempoolService(), $"/WasabiCoordinator:{Constants.BackendMajorVersion.ToString()}/");
+ await p2pNode.ConnectAsync(cancel).ConfigureAwait(false);
+ BlockNotifier = new BlockNotifier(TimeSpan.FromSeconds(7), RpcClient, p2pNode);
BlockNotifier.Start();
-
- try
- {
- Logger.LogInfo("TCP Connection succeeded, handshaking...");
- node.VersionHandshake(Constants.LocalBackendNodeRequirements, handshakeTimeout.Token);
- var peerServices = node.PeerVersion.Services;
-
- if (!peerServices.HasFlag(NodeServices.Network) && !peerServices.HasFlag(NodeServices.NODE_NETWORK_LIMITED))
- {
- throw new InvalidOperationException("Wasabi cannot use the local node because it does not provide blocks.");
- }
-
- Logger.LogInfo("Handshake completed successfully.");
-
- if (!node.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.");
- }
- LocalNode = node;
- }
- 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;
- }
}
private async Task AssertRpcNodeFullyInitializedAsync()

MODIFY
WalletWasabi.Backend/InitConfigStartupTask.cs
WalletWasabi.Backend/InitConfigStartupTask.cs
@@ -48,7 +48,7 @@ namespace WalletWasabi.Backend
hostOrUri: host,
network: config.Network);
- await Global.InitializeAsync(config, roundConfig, rpc);
+ await Global.InitializeAsync(config, roundConfig, rpc, cancellationToken);
try
{

MODIFY
WalletWasabi.Backend/Startup.cs
WalletWasabi.Backend/Startup.cs
@@ -115,10 +115,10 @@ namespace WalletWasabi.Backend
Logger.LogInfo($"{nameof(global.RoundConfigWatcher)} is disposed.");
}
- if (global.LocalNode != null)
+ if (global.P2pNode != null)
{
- global.DisconnectDisposeNullLocalNode();
- Logger.LogInfo($"{nameof(global.LocalNode)} is disposed.");
+ global.P2pNode.Dispose();
+ Logger.LogInfo($"{nameof(global.P2pNode)} is disposed.");
}
Logger.LogSoftwareStopped("Wasabi Backend");

MODIFY
WalletWasabi.Gui/Global.cs
WalletWasabi.Gui/Global.cs
@@ -232,7 +232,8 @@ namespace WalletWasabi.Gui
if (Config.StartLocalBitcoinCoreOnStartup)
{
BitcoinCoreNode = await CoreNode
- .CreateAsync(new CoreNodeParams(
+ .CreateAsync(
+ new CoreNodeParams(
Network,
BitcoinStore.MempoolService,
Config.LocalBitcoinCoreDataDir,
@@ -241,7 +242,9 @@ namespace WalletWasabi.Gui
EndPointStrategy.Custom(Config.GetBitcoinP2pEndPoint()),
EndPointStrategy.Default(Network, EndPointType.Rpc),
txIndex: null,
- prune: null))
+ prune: null,
+ userAgent: $"/WasabiClient:{Constants.ClientVersion.ToString()}/"),
+ CancellationToken.None)
.ConfigureAwait(false);
RpcMonitor.RpcClient = BitcoinCoreNode.RpcClient;

MODIFY
WalletWasabi.Tests/Helpers/TestNodeBuilder.cs
WalletWasabi.Tests/Helpers/TestNodeBuilder.cs
@@ -4,6 +4,7 @@ using System.Collections.Generic;
using System.IO;
using System.Runtime.CompilerServices;
using System.Text;
+using System.Threading;
using System.Threading.Tasks;
using WalletWasabi.BitcoinCore;
using WalletWasabi.BitcoinCore.Endpointing;
@@ -18,7 +19,8 @@ namespace WalletWasabi.Tests.Helpers
public static async Task<CoreNode> CreateAsync([CallerFilePath]string callerFilePath = null, [CallerMemberName]string callerMemberName = null, string additionalFolder = null, MempoolService mempoolService = null)
{
var network = Network.RegTest;
- return await CoreNode.CreateAsync(new CoreNodeParams(
+ return await CoreNode.CreateAsync(
+ new CoreNodeParams(
network,
mempoolService ?? new MempoolService(),
Path.Combine(Global.Instance.DataDir, EnvironmentHelpers.ExtractFileName(callerFilePath), callerMemberName, additionalFolder ?? ""),
@@ -27,7 +29,9 @@ namespace WalletWasabi.Tests.Helpers
EndPointStrategy.Random,
EndPointStrategy.Random,
txIndex: 1,
- prune: 0));
+ prune: 0,
+ userAgent: $"/WasabiClient:{Constants.ClientVersion.ToString()}/"),
+ CancellationToken.None);
}
}
}

MODIFY
WalletWasabi/BitcoinCore/CoreNode.cs
WalletWasabi/BitcoinCore/CoreNode.cs
@@ -37,11 +37,10 @@ namespace WalletWasabi.BitcoinCore
public MempoolService MempoolService { get; private set; }
public CoreConfig Config { get; private set; }
- public TrustedP2pBehavior TrustedP2pBehavior => P2pNode.Behaviors.Find<TrustedP2pBehavior>();
- public Node P2pNode { get; private set; }
+ public P2pNode P2pNode { get; private set; }
public BlockNotifier BlockNotifier { get; private set; }
- public static async Task<CoreNode> CreateAsync(CoreNodeParams coreNodeParams)
+ public static async Task<CoreNode> CreateAsync(CoreNodeParams coreNodeParams, CancellationToken cancel)
{
Guard.NotNull(nameof(coreNodeParams), coreNodeParams);
using (BenchmarkLogger.Measure())
@@ -190,42 +189,16 @@ namespace WalletWasabi.BitcoinCore
Logger.LogInfo("Started Bitcoin Core.");
}
- using var handshakeTimeout = new CancellationTokenSource();
- handshakeTimeout.CancelAfter(TimeSpan.FromSeconds(21));
- var nodeConnectionParameters = new NodeConnectionParameters()
- {
- UserAgent = $"/WasabiClient:{Constants.ClientVersion.ToString()}/",
- ConnectCancellation = handshakeTimeout.Token,
- IsRelay = true
- };
+ coreNode.P2pNode = new P2pNode(coreNode.Network, coreNode.P2pEndPoint, coreNode.MempoolService, coreNodeParams.UserAgent);
+ await coreNode.P2pNode.ConnectAsync(cancel).ConfigureAwait(false);
- nodeConnectionParameters.TemplateBehaviors.Add(new TrustedP2pBehavior(coreNode.MempoolService));
- coreNode.P2pNode = await Node.ConnectAsync(coreNode.Network, coreNode.P2pEndPoint, nodeConnectionParameters).ConfigureAwait(false);
- coreNode.P2pNode.VersionHandshake();
- coreNode.P2pNode.StateChanged += coreNode.P2pNode_StateChanged;
- coreNode.P2pNodeStateChangedSubscribed = true;
- coreNode.MempoolService.TrustedNodeMode = coreNode.P2pNode.IsConnected;
-
- coreNode.BlockNotifier = new BlockNotifier(TimeSpan.FromSeconds(7), coreNode.RpcClient, coreNode.TrustedP2pBehavior);
+ coreNode.BlockNotifier = new BlockNotifier(TimeSpan.FromSeconds(7), coreNode.RpcClient, coreNode.P2pNode);
coreNode.BlockNotifier.Start();
return coreNode;
}
}
- private bool P2pNodeStateChangedSubscribed { get; set; }
-
- private void P2pNode_StateChanged(Node node, NodeState oldState)
- {
- var isConnected = node.IsConnected;
- var trustedNodeMode = MempoolService.TrustedNodeMode;
- if (trustedNodeMode != isConnected)
- {
- MempoolService.TrustedNodeMode = isConnected;
- Logger.LogInfo($"CoreNode connection state changed. Triggered {nameof(MempoolService)}.{nameof(MempoolService.TrustedNodeMode)} to be {MempoolService.TrustedNodeMode}");
- }
- }
-
public static async Task<Version> GetVersionAsync(CancellationToken cancel)
{
var arguments = "-version";
@@ -267,38 +240,7 @@ namespace WalletWasabi.BitcoinCore
await BlockNotifier.StopAsync().ConfigureAwait(false);
}
- if (P2pNodeStateChangedSubscribed)
- {
- P2pNode.StateChanged -= P2pNode_StateChanged;
- }
-
- if (P2pNode != null)
- {
- try
- {
- P2pNode?.Disconnect();
- }
- catch (Exception ex)
- {
- Logger.LogDebug(ex);
- }
- finally
- {
- try
- {
P2pNode?.Dispose();
- }
- catch (Exception ex)
- {
- Logger.LogDebug(ex);
- }
- finally
- {
- P2pNode = null;
- Logger.LogInfo("P2p Bitcoin node is disconnected.");
- }
- }
- }
_disposedValue = true;
}
}

MODIFY
WalletWasabi/BitcoinCore/CoreNodeParams.cs
WalletWasabi/BitcoinCore/CoreNodeParams.cs
@@ -20,7 +20,8 @@ namespace WalletWasabi.BitcoinCore
EndPointStrategy p2pEndPointStrategy,
EndPointStrategy rpcEndPointStrategy,
int? txIndex,
- int? prune)
+ int? prune,
+ string userAgent)
{
Network = Guard.NotNull(nameof(network), network);
MempoolService = Guard.NotNull(nameof(mempoolService), mempoolService);
@@ -31,6 +32,7 @@ namespace WalletWasabi.BitcoinCore
RpcEndPointStrategy = Guard.NotNull(nameof(rpcEndPointStrategy), rpcEndPointStrategy);
TxIndex = txIndex;
Prune = prune;
+ UserAgent = Guard.NotNullOrEmptyOrWhitespace(nameof(userAgent), userAgent, trim: true);
}
public string DataDir { get; }
@@ -40,6 +42,7 @@ namespace WalletWasabi.BitcoinCore
public bool TryDeleteDataDir { get; }
public int? TxIndex { get; }
public int? Prune { get; }
+ public string UserAgent { get; }
public EndPointStrategy P2pEndPointStrategy { get; }
public EndPointStrategy RpcEndPointStrategy { get; }
}

ADD
WalletWasabi/BitcoinCore/P2pNode.cs
+using NBitcoin;
+using NBitcoin.Protocol;
+using System;
+using System.Collections.Generic;
+using System.Net;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+using WalletWasabi.Blockchain.Blocks;
+using WalletWasabi.Blockchain.Mempool;
+using WalletWasabi.Blockchain.P2p;
+using WalletWasabi.Helpers;
+using WalletWasabi.Logging;
+
+namespace WalletWasabi.BitcoinCore
+{
+ public class P2pNode : IDisposable
+ {
+ private Node Node { get; set; }
+ private TrustedP2pBehavior TrustedP2pBehavior { get; set; }
+ public Network Network { get; }
+ public EndPoint EndPoint { get; }
+ public MempoolService MempoolService { get; }
+
+ public event EventHandler<uint256> BlockInv;
+
+ public P2pNode(Network network, EndPoint endPoint, MempoolService mempoolService, string userAgent)
+ {
+ Network = Guard.NotNull(nameof(network), network);
+ EndPoint = Guard.NotNull(nameof(endPoint), endPoint);
+ MempoolService = Guard.NotNull(nameof(mempoolService), mempoolService);
+ UserAgent = Guard.NotNullOrEmptyOrWhitespace(nameof(userAgent), userAgent, trim: true);
+ }
+
+ public async Task ConnectAsync(CancellationToken cancel)
+ {
+ using var handshakeTimeout = new CancellationTokenSource();
+ using var linked = CancellationTokenSource.CreateLinkedTokenSource(handshakeTimeout.Token, cancel);
+ handshakeTimeout.CancelAfter(TimeSpan.FromSeconds(21));
+ var parameters = new NodeConnectionParameters()
+ {
+ UserAgent = UserAgent,
+ ConnectCancellation = linked.Token,
+ IsRelay = true
+ };
+
+ parameters.TemplateBehaviors.Add(new TrustedP2pBehavior(MempoolService));
+
+ Node = await Node.ConnectAsync(Network, EndPoint, parameters).ConfigureAwait(false);
+ Node.VersionHandshake();
+
+ if (!Node.PeerVersion.Services.HasFlag(NodeServices.Network))
+ {
+ throw new InvalidOperationException("Wasabi cannot use the local node because it does not provide blocks.");
+ }
+
+ if (!Node.IsConnected)
+ {
+ throw new InvalidOperationException(
+ $"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.");
+ }
+
+ Node.StateChanged += P2pNode_StateChanged;
+ P2pNodeStateChangedSubscribed = true;
+ MempoolService.TrustedNodeMode = Node.IsConnected;
+
+ TrustedP2pBehavior = Node.Behaviors.Find<TrustedP2pBehavior>();
+ TrustedP2pBehavior.BlockInv += TrustedP2pBehavior_BlockInv;
+ TrustedP2pBehaviorBlockInvSubscribed = true;
+ }
+
+ private bool TrustedP2pBehaviorBlockInvSubscribed { get; set; }
+
+ private void TrustedP2pBehavior_BlockInv(object sender, uint256 e)
+ {
+ BlockInv?.Invoke(this, e);
+ }
+
+ private bool P2pNodeStateChangedSubscribed { get; set; }
+ public string UserAgent { get; }
+
+ private void P2pNode_StateChanged(Node node, NodeState oldState)
+ {
+ var isConnected = node.IsConnected;
+ var trustedNodeMode = MempoolService.TrustedNodeMode;
+ if (trustedNodeMode != isConnected)
+ {
+ MempoolService.TrustedNodeMode = isConnected;
+ Logger.LogInfo($"CoreNode connection state changed. Triggered {nameof(MempoolService)}.{nameof(MempoolService.TrustedNodeMode)} to be {MempoolService.TrustedNodeMode}");
+ }
+ }
+
+ #region IDisposable Support
+
+ private volatile bool _disposedValue = false; // To detect redundant calls
+
+ protected virtual void Dispose(bool disposing)
+ {
+ if (!_disposedValue)
+ {
+ if (disposing)
+ {
+ if (TrustedP2pBehaviorBlockInvSubscribed)
+ {
+ TrustedP2pBehavior.BlockInv -= TrustedP2pBehavior_BlockInv;
+ }
+ if (P2pNodeStateChangedSubscribed)
+ {
+ Node.StateChanged -= P2pNode_StateChanged;
+ }
+
+ if (Node != null)
+ {
+ try
+ {
+ Node?.Disconnect();
+ }
+ catch (Exception ex)
+ {
+ Logger.LogDebug(ex);
+ }
+ finally
+ {
+ try
+ {
+ Node?.Dispose();
+ }
+ catch (Exception ex)
+ {
+ Logger.LogDebug(ex);
+ }
+ finally
+ {
+ Node = null;
+ Logger.LogInfo("P2p Bitcoin node is disconnected.");
+ }
+ }
+ }
+ }
+
+ _disposedValue = true;
+ }
+ }
+
+ // This code added to correctly implement the disposable pattern.
+ public void Dispose()
+ {
+ // Do not change this code. Put cleanup code in Dispose(bool disposing) above.
+ Dispose(true);
+ }
+
+ #endregion IDisposable Support
+ }
+}

MODIFY
WalletWasabi/Blockchain/Blocks/BlockNotifier.cs
WalletWasabi/Blockchain/Blocks/BlockNotifier.cs
@@ -8,6 +8,7 @@ using System.Text;
using System.Threading;
using System.Threading.Tasks;
using WalletWasabi.Bases;
+using WalletWasabi.BitcoinCore;
using WalletWasabi.Blockchain.P2p;
using WalletWasabi.Helpers;
using WalletWasabi.Logging;
@@ -22,20 +23,18 @@ namespace WalletWasabi.Blockchain.Blocks
public event EventHandler<BlockHeader> OnReorg;
public RPCClient RpcClient { get; set; }
- public TrustedP2pBehavior TrustedP2pBehavior { get; }
private List<BlockHeader> ProcessedBlocks { get; }
+ public P2pNode P2pNode { get; }
- public BlockNotifier(TimeSpan period, RPCClient rpcClient, TrustedP2pBehavior trustedP2pBehavior) : base(period, null)
+ public BlockNotifier(TimeSpan period, RPCClient rpcClient, P2pNode p2pNode) : base(period, null)
{
RpcClient = Guard.NotNull(nameof(rpcClient), rpcClient);
- TrustedP2pBehavior = Guard.NotNull(nameof(trustedP2pBehavior), trustedP2pBehavior);
-
+ P2pNode = p2pNode;
ProcessedBlocks = new List<BlockHeader>();
-
- TrustedP2pBehavior.BlockInv += P2pNotifier_BlockInv;
+ P2pNode.BlockInv += P2pNode_BlockInv;
}
- private void P2pNotifier_BlockInv(object sender, uint256 blockHash)
+ private void P2pNode_BlockInv(object sender, uint256 blockHash)
{
TriggerRound();
}
@@ -156,7 +155,7 @@ namespace WalletWasabi.Blockchain.Blocks
public new async Task StopAsync()
{
- TrustedP2pBehavior.BlockInv -= P2pNotifier_BlockInv;
+ P2pNode.BlockInv -= P2pNode_BlockInv;
await base.StopAsync().ConfigureAwait(false);
}
}

Create P2pNode