public NetworkPeer(NetworkAddress peerAddress, Network network, NetworkPeerConnectionParameters parameters, IDateTimeProvider dateTimeProvider, ILoggerFactory loggerFactory)
        {
            this.loggerFactory = loggerFactory;
            this.logger        = loggerFactory.CreateLogger(this.GetType().FullName, $"[{peerAddress.Endpoint}] ");

            this.logger.LogTrace("()");
            this.dateTimeProvider = dateTimeProvider;

            parameters       = parameters ?? new NetworkPeerConnectionParameters();
            this.Inbound     = false;
            this.Behaviors   = new NetworkPeerBehaviorsCollection(this);
            this.MyVersion   = parameters.CreateVersion(peerAddress.Endpoint, network, this.dateTimeProvider.GetTimeOffset());
            this.Network     = network;
            this.PeerAddress = peerAddress;
            this.LastSeen    = peerAddress.Time.UtcDateTime;

            var socket = new Socket(AddressFamily.InterNetworkV6, SocketType.Stream, ProtocolType.Tcp);

            socket.SetSocketOption(SocketOptionLevel.IPv6, SocketOptionName.IPv6Only, false);

            this.connection = new NetworkPeerConnection(this, socket, this.dateTimeProvider, this.loggerFactory);

            socket.ReceiveBufferSize = parameters.ReceiveBufferSize;
            socket.SendBufferSize    = parameters.SendBufferSize;
            try
            {
                using (var completedEvent = new ManualResetEvent(false))
                {
                    using (var nodeSocketEventManager = NodeSocketEventManager.Create(completedEvent, peerAddress.Endpoint))
                    {
                        this.logger.LogTrace("Connecting to '{0}'.", peerAddress.Endpoint);

                        // If the socket connected straight away (synchronously) unblock all threads.
                        if (!socket.ConnectAsync(nodeSocketEventManager.SocketEvent))
                        {
                            completedEvent.Set();
                        }

                        // Otherwise wait for the socket connection to complete OR if the operation got cancelled.
                        WaitHandle.WaitAny(new WaitHandle[] { completedEvent, parameters.ConnectCancellation.WaitHandle });

                        parameters.ConnectCancellation.ThrowIfCancellationRequested();

                        if (nodeSocketEventManager.SocketEvent.SocketError != SocketError.Success)
                        {
                            throw new SocketException((int)nodeSocketEventManager.SocketEvent.SocketError);
                        }

                        var remoteEndpoint = (IPEndPoint)(socket.RemoteEndPoint ?? nodeSocketEventManager.SocketEvent.RemoteEndPoint);
                        this.RemoteSocketAddress  = remoteEndpoint.Address;
                        this.RemoteSocketEndpoint = remoteEndpoint;
                        this.RemoteSocketPort     = remoteEndpoint.Port;

                        this.State       = NetworkPeerState.Connected;
                        this.ConnectedAt = this.dateTimeProvider.GetUtcNow();

                        this.logger.LogTrace("Outbound connection to '{0}' established.", peerAddress.Endpoint);
                    }
                }
            }
            catch (OperationCanceledException)
            {
                this.logger.LogTrace("Connection to '{0}' cancelled.", peerAddress.Endpoint);
                Utils.SafeCloseSocket(socket);
                this.State = NetworkPeerState.Offline;

                throw;
            }
            catch (Exception ex)
            {
                this.logger.LogTrace("Exception occurred: {0}", ex.ToString());
                Utils.SafeCloseSocket(socket);
                this.DisconnectReason = new NetworkPeerDisconnectReason()
                {
                    Reason    = "Unexpected exception while connecting to socket",
                    Exception = ex
                };

                this.State = NetworkPeerState.Failed;

                throw;
            }

            this.InitDefaultBehaviors(parameters);
            this.connection.BeginListen();

            this.logger.LogTrace("(-)");
        }
        public void BeginListen()
        {
            this.logger.LogTrace("()");

            this.Disconnected = new ManualResetEvent(false);
            this.Cancel       = new CancellationTokenSource();

            new Thread(() =>
            {
                this.logger.LogTrace("()");
                SentMessage processing       = null;
                Exception unhandledException = null;

                try
                {
                    using (var completedEvent = new ManualResetEvent(false))
                    {
                        using (var socketEventManager = NodeSocketEventManager.Create(completedEvent))
                        {
                            socketEventManager.SocketEvent.SocketFlags = SocketFlags.None;

                            foreach (SentMessage kv in this.Messages.GetConsumingEnumerable(this.Cancel.Token))
                            {
                                processing      = kv;
                                Payload payload = kv.Payload;
                                var message     = new Message
                                {
                                    Magic   = this.Peer.Network.Magic,
                                    Payload = payload
                                };

                                this.logger.LogTrace("Sending message: '{0}'", message);

                                using (MemoryStream ms = new MemoryStream())
                                {
                                    message.ReadWrite(new BitcoinStream(ms, true)
                                    {
                                        ProtocolVersion    = this.Peer.Version,
                                        TransactionOptions = this.Peer.SupportedTransactionOptions
                                    });

                                    byte[] bytes = ms.ToArrayEfficient();

                                    socketEventManager.SocketEvent.SetBuffer(bytes, 0, bytes.Length);
                                    this.Peer.Counter.AddWritten(bytes.Length);
                                }

                                completedEvent.Reset();
                                if (!this.Socket.SendAsync(socketEventManager.SocketEvent))
                                {
                                    Utils.SafeSet(completedEvent);
                                }

                                WaitHandle.WaitAny(new WaitHandle[] { completedEvent, this.Cancel.Token.WaitHandle }, -1);
                                if (!this.Cancel.Token.IsCancellationRequested)
                                {
                                    if (socketEventManager.SocketEvent.SocketError != SocketError.Success)
                                    {
                                        throw new SocketException((int)socketEventManager.SocketEvent.SocketError);
                                    }

                                    processing.Completion.SetResult(true);
                                    processing = null;
                                }
                            }
                        }
                    }
                }
                catch (OperationCanceledException)
                {
                    this.logger.LogTrace("Sending cancelled.");
                }
                catch (Exception ex)
                {
                    this.logger.LogTrace("Exception occurred: '{0}'", ex.ToString());
                    unhandledException = ex;
                }

                if (processing != null)
                {
                    this.Messages.Add(processing);
                }

                foreach (SentMessage pending in this.Messages)
                {
                    this.logger.LogTrace("Connection terminated before message '{0}' could be sent.", pending.Payload?.Command);
                    pending.Completion.SetException(new OperationCanceledException("The peer has been disconnected"));
                }

                this.Messages = new BlockingCollection <SentMessage>(new ConcurrentQueue <SentMessage>());

                this.logger.LogDebug("Terminating sending thread.");
                this.EndListen(unhandledException);

                this.logger.LogTrace("(-)");
            }).Start();

            new Thread(() =>
            {
                this.logger.LogTrace("()");

                this.ListenerThreadId = Thread.CurrentThread.ManagedThreadId;

                this.logger.LogTrace("Start listenting.");
                Exception unhandledException = null;
                byte[] buffer = this.Peer.ReuseBuffer ? new byte[1024 * 1024] : null;
                try
                {
                    using (var stream = new NetworkStream(this.Socket, false))
                    {
                        while (!this.Cancel.Token.IsCancellationRequested)
                        {
                            PerformanceCounter counter;

                            Message message = Message.ReadNext(stream, this.Peer.Network, this.Peer.Version, this.Cancel.Token, buffer, out counter);

                            this.logger.LogTrace("Receiving message: '{0}'", message);

                            this.Peer.LastSeen = this.dateTimeProvider.GetUtcNow();
                            this.Peer.Counter.Add(counter);
                            this.Peer.OnMessageReceived(new IncomingMessage()
                            {
                                Message     = message,
                                Socket      = this.Socket,
                                Length      = counter.ReadBytes,
                                NetworkPeer = this.Peer
                            });
                        }
                    }
                }
                catch (OperationCanceledException)
                {
                    this.logger.LogTrace("Listening cancelled.");
                }
                catch (Exception ex)
                {
                    this.logger.LogTrace("Exception occurred: {0}", ex);
                    unhandledException = ex;
                }

                this.logger.LogDebug("Terminating listening thread.");
                this.EndListen(unhandledException);

                this.logger.LogTrace("(-)");
            }).Start();

            this.logger.LogTrace("(-)");
        }