async Task <BitcoinMessage> ReadMessageAsync(CancellationToken cancellation)
        {
            // First find and read the magic. This will block until a valid magic has been read, or throw if the connection is lost / stream ended
            await ReadMagicAsync(ChatClientConfiguration.NetworkMagicBytes, cancellation).ConfigureAwait(false);


            // Then read the rest of the header (we read the magic already), which is formed of command, length, and a required checksum.
            int headerExMagicSize = BitcoinMessage.ModernHeaderSize - BitcoinMessage.MagicSize;

            var completeHeaderBuffer =
                new byte[BitcoinMessage
                         .ModernHeaderSize]; // magic already read, now read: 12 bytes command, 4 bytes payload length, 4 bytes checksum = 20 bytes

            if (!await this
                .ReadBytesAsync(completeHeaderBuffer, BitcoinMessage.MagicSize, headerExMagicSize, cancellation)
                .ConfigureAwait(false))
            {
                throw new ConnectedPeerException("Stream ended before the header could be read.", null);
            }

            Buffer.BlockCopy(ChatClientConfiguration.NetworkMagicBytes, 0, completeHeaderBuffer, 0, BitcoinMessage.MagicSize);

            var command = Encoding.ASCII
                          .GetString(completeHeaderBuffer, BitcoinMessage.MagicSize, BitcoinMessage.CommandSize).TrimEnd('\0');


            // Then extract the length, which is the message payload size.
            uint payloadLength = BitConverter.ToUInt32(completeHeaderBuffer,
                                                       BitcoinMessage.MagicSize + BitcoinMessage.CommandSize);

            if (payloadLength > 0x00400000)
            {
                throw new ProtocolViolationException("Message payload too big (over 0x00400000 bytes)");
            }

            this._logger.LogInformation($"Received message '{command}', payload length: {payloadLength} bytes.");

            var checkSumBytes = new byte[4];

            Buffer.BlockCopy(completeHeaderBuffer,
                             BitcoinMessage.MagicSize + BitcoinMessage.CommandSize + BitcoinMessage.PayloadLengthSize, checkSumBytes,
                             0, BitcoinMessage.ChecksumSize);


            byte[] payloadBuffer = new byte[payloadLength];
            if (!await this.ReadBytesAsync(payloadBuffer, 0, (int)payloadLength, cancellation).ConfigureAwait(false))
            {
                throw new ConnectedPeerException("Stream ended before the payload could be read.", null);
            }

            byte[] completeWithHeaderAndPayload = new byte[BitcoinMessage.ModernHeaderSize + payloadLength];
            Buffer.BlockCopy(completeHeaderBuffer, 0, completeWithHeaderAndPayload, 0, completeHeaderBuffer.Length);
            Buffer.BlockCopy(payloadBuffer, 0, completeWithHeaderAndPayload, completeHeaderBuffer.Length,
                             payloadBuffer.Length);

            var ret = new BitcoinMessage(command, payloadBuffer, checkSumBytes);

            return(ret);
        }
        public async Task VersionHandshakeAsync(string userAgent, byte[] nonce, CancellationToken cancellationToken)
        {
            try
            {
                BitcoinVersionPayload outgoingVersionPayload = PayloadFactory.CreateVersionPayload(userAgent, nonce,
                                                                                                   (IPEndPoint)this._tcpClient.Client.LocalEndPoint,
                                                                                                   (IPEndPoint)this._tcpClient.Client.RemoteEndPoint);

                var myVersionMessage = new BitcoinMessage("version", outgoingVersionPayload.Serialize());
                this._logger.LogInformation("Sending own version.");
                await WriteAsync(myVersionMessage.Serialize());

                this._logger.LogInformation("'version' sent.");

                // do it like this so that there is no specific expected order for version and verack, but expect both.
                while (this.PeerVersionPayload == null || this.PeerVerAckReceived == false)
                {
                    var reply = await ReadMessageAsync(cancellationToken);

                    if (reply.Command == "version")
                    {
                        this.PeerVersionPayload = new BitcoinVersionPayload(reply.PayloadBytes);
                        this._logger.LogInformation(
                            $"Version payload decoded from {this.PeerVersionPayload.UserAgent.Text} at {this.PeerVersionPayload.Sender}.");
                        this._logger.LogInformation($"Setting own address to {this.PeerVersionPayload.Receiver}.");


                        this.SelfFromPeer = this.PeerVersionPayload.Receiver.ToPeer();

                        this._logger.LogInformation("Sending version acknowledgement.");
                        await WriteAsync(new BitcoinMessage("verack", new byte[0]).Serialize());

                        this._logger.LogInformation("'verack' sent.");
                    }
                    else if (reply.Command == "verack")
                    {
                        this.PeerVerAckReceived = true;
                    }
                    else
                    {
                        throw new InvalidOperationException(
                                  $"Received unexpected message '{reply.Command}' during handshake.");
                    }
                }
            }
            catch (Exception e)
            {
                DisposeAndThrow(e);
            }
        }