/// <summary>
        /// Queries <see cref="AppProtocolVersion"/> of given <see cref="BoundPeer"/>.
        /// </summary>
        /// <param name="peer">The <see cref="BoundPeer"/> to query
        /// <see cref="AppProtocolVersion"/>.</param>
        /// <param name="timeout">Timeout value for request.</param>
        /// <returns><see cref="AppProtocolVersion"/> of given peer. </returns>
        public static AppProtocolVersion QueryAppProtocolVersion(
            this BoundPeer peer,
            TimeSpan?timeout = null
            )
        {
            using var dealerSocket = new DealerSocket(ToNetMQAddress(peer));
            var          key  = new PrivateKey();
            var          ping = new Ping();
            var          netMQMessageCodec = new NetMQMessageCodec();
            NetMQMessage request           = netMQMessageCodec.Encode(
                ping,
                key,
                new Peer(key.PublicKey),
                DateTimeOffset.UtcNow,
                default
                );

            TimeSpan timeoutNotNull = timeout ?? TimeSpan.FromSeconds(5);

            if (dealerSocket.TrySendMultipartMessage(timeoutNotNull, request))
            {
                var response = new NetMQMessage();
                if (dealerSocket.TryReceiveMultipartMessage(timeoutNotNull, ref response))
                {
                    return(AppProtocolVersion.FromToken(response.First.ConvertToString()));
                }
            }

            throw new TimeoutException(
                      $"Peer[{peer}] didn't respond within the specified time[{timeout}]."
                      );
        }
        GenerateLibplanetNodeServiceProperties(
            string appProtocolVersionToken = null,
            string genesisBlockPath        = null,
            string swarmHost                          = null,
            ushort?swarmPort                          = null,
            int minimumDifficulty                     = 5000000,
            string privateKeyString                   = null,
            string storeType                          = null,
            string storePath                          = null,
            int storeStateCacheSize                   = 100,
            string[] iceServerStrings                 = null,
            string[] peerStrings                      = null,
            bool noTrustedStateValidators             = false,
            string[] trustedAppProtocolVersionSigners = null,
            bool noMiner = false,
            bool render  = false)
        {
            var privateKey = string.IsNullOrEmpty(privateKeyString)
                ? new PrivateKey()
                : new PrivateKey(ByteUtil.ParseHex(privateKeyString));

            peerStrings ??= Array.Empty <string>();
            iceServerStrings ??= Array.Empty <string>();

            var iceServers = iceServerStrings.Select(LoadIceServer).ToImmutableArray();
            var peers      = peerStrings.Select(LoadPeer).ToImmutableArray();

            IImmutableSet <Address> trustedStateValidators;

            if (noTrustedStateValidators)
            {
                trustedStateValidators = ImmutableHashSet <Address> .Empty;
            }
            else
            {
                trustedStateValidators = peers.Select(p => p.Address).ToImmutableHashSet();
            }

            return(new LibplanetNodeServiceProperties <NineChroniclesActionType>
            {
                Host = swarmHost,
                Port = swarmPort,
                AppProtocolVersion = AppProtocolVersion.FromToken(appProtocolVersionToken),
                TrustedAppProtocolVersionSigners = trustedAppProtocolVersionSigners
                                                   ?.Select(s => new PublicKey(ByteUtil.ParseHex(s)))
                                                   ?.ToHashSet(),
                GenesisBlockPath = genesisBlockPath,
                NoMiner = noMiner,
                PrivateKey = privateKey,
                IceServers = iceServers,
                Peers = peers,
                TrustedStateValidators = trustedStateValidators,
                StoreType = storeType,
                StorePath = storePath,
                StoreStatesCacheSize = storeStateCacheSize,
                MinimumDifficulty = minimumDifficulty,
                Render = render
            });
        }
        GenerateLibplanetNodeServiceProperties(
            string appProtocolVersionToken = null,
            string genesisBlockPath        = null,
            string swarmHost          = null,
            ushort?swarmPort          = null,
            int minimumDifficulty     = 5000000,
            string privateKeyString   = null,
            string storeType          = null,
            string storePath          = null,
            int storeStateCacheSize   = 100,
            string[] iceServerStrings = null,
            string[] peerStrings      = null,
            string[] trustedAppProtocolVersionSigners = null,
            bool noMiner            = false,
            bool render             = false,
            int workers             = 5,
            int confirmations       = 0,
            int maximumTransactions = 100)
        {
            var privateKey = string.IsNullOrEmpty(privateKeyString)
                ? new PrivateKey()
                : new PrivateKey(ByteUtil.ParseHex(privateKeyString));

            peerStrings ??= Array.Empty <string>();
            iceServerStrings ??= Array.Empty <string>();

            var iceServers = iceServerStrings.Select(LoadIceServer).ToImmutableArray();
            var peers      = peerStrings.Select(LoadPeer).ToImmutableArray();

            return(new LibplanetNodeServiceProperties <NineChroniclesActionType>
            {
                Host = swarmHost,
                Port = swarmPort,
                AppProtocolVersion = AppProtocolVersion.FromToken(appProtocolVersionToken),
                TrustedAppProtocolVersionSigners = trustedAppProtocolVersionSigners
                                                   ?.Select(s => new PublicKey(ByteUtil.ParseHex(s)))
                                                   ?.ToHashSet(),
                GenesisBlockPath = genesisBlockPath,
                NoMiner = noMiner,
                PrivateKey = privateKey,
                IceServers = iceServers,
                Peers = peers,
                StoreType = storeType,
                StorePath = storePath,
                StoreStatesCacheSize = storeStateCacheSize,
                MinimumDifficulty = minimumDifficulty,
                Render = render,
                Workers = workers,
                Confirmations = Math.Max(confirmations, 0),
                MaximumTransactions = maximumTransactions,
            });
        }
Esempio n. 4
0
        private AppProtocolVersion ParseAppProtocolVersionToken(string?token)
        {
            if (token is null)
            {
                token = Console.ReadLine();
            }

            try
            {
                return(AppProtocolVersion.FromToken(token.Trim()));
            }
            catch (FormatException e)
            {
                throw Utils.Error($"Not a valid app protocol version token.  {e.Message}");
            }
        }
Esempio n. 5
0
        private void InitAgent(IEnumerable <IRenderer <PolymorphicAction <ActionBase> > > renderers)
        {
            var options            = GetOptions(CommandLineOptionsJsonPath);
            var privateKey         = GetPrivateKey(options);
            var peers              = options.Peers.Select(LoadPeer).ToImmutableList();
            var iceServers         = options.IceServers.Select(LoadIceServer).ToImmutableList();
            var host               = options.Host;
            int?port               = options.Port;
            var storagePath        = options.StoragePath ?? DefaultStoragePath;
            var appProtocolVersion = options.AppProtocolVersion is null
                ? default
                : AppProtocolVersion.FromToken(options.AppProtocolVersion);
            var trustedAppProtocolVersionSigners = options.TrustedAppProtocolVersionSigners
                                                   .Select(s => new PublicKey(ByteUtil.ParseHex(s)));

            if (options.Logging)
            {
                Log.Logger = new LoggerConfiguration()
                             .MinimumLevel.Debug()
                             .WriteTo.Console()
                             .CreateLogger();
            }

            Init(
                privateKey,
                storagePath,
                peers,
                iceServers,
                host,
                port,
                appProtocolVersion,
                trustedAppProtocolVersionSigners,
                renderers
                );

            _miner = options.NoMiner ? null : CoMiner();

            StartSystemCoroutines();
            StartNullableCoroutine(_miner);
        }
Esempio n. 6
0
        public static Message Parse(
            NetMQMessage raw,
            bool reply,
            AppProtocolVersion localVersion,
            IImmutableSet <PublicKey> trustedAppProtocolVersionSigners,
            DifferentAppProtocolVersionEncountered differentAppProtocolVersionEncountered)
        {
            if (raw.FrameCount == 0)
            {
                throw new ArgumentException("Can't parse empty NetMQMessage.");
            }

            // (reply == true)  [version, type, peer, sign, frames...]
            // (reply == false) [identity, version, type, peer, sign, frames...]
            NetMQFrame[] remains = reply ? raw.ToArray() : raw.Skip(1).ToArray();

            var versionToken = remains[(int)MessageFrame.Version].ConvertToString();

            AppProtocolVersion remoteVersion = AppProtocolVersion.FromToken(versionToken);
            Peer remotePeer = null;

            try
            {
                remotePeer = DeserializePeer(remains[(int)MessageFrame.Peer].ToByteArray());
            }
            catch (Exception)
            {
                // If failed to find out remotePeer, leave it null.
            }

            if (!IsAppProtocolVersionValid(
                    remotePeer,
                    localVersion,
                    remoteVersion,
                    trustedAppProtocolVersionSigners,
                    differentAppProtocolVersionEncountered))
            {
                throw new DifferentAppProtocolVersionException(
                          "Received message's version is not valid.",
                          reply ? null : raw[0].Buffer.ToArray(),
                          localVersion,
                          remoteVersion);
            }

            var rawType = (MessageType)remains[(int)MessageFrame.Type].ConvertToInt32();
            var peer    = remains[(int)MessageFrame.Peer].ToByteArray();

            byte[] signature = remains[(int)MessageFrame.Sign].ToByteArray();

            NetMQFrame[] body = remains.Skip(CommonFrames).ToArray();

            // FIXME: The below code is too repetitive and prone to miss to add, which means,
            // when you add a new message type, you adds an enum member to MessageType and
            // a corresponding subclass of Message, but misses to add that correspondence here,
            // you may take a long time to be aware you've missed here, because the code is still
            // built well and it looks like just Swarm<T> silently ignore new messages.
            // At least this correspondence map should not be here.
            var types = new Dictionary <MessageType, Type>
            {
                { MessageType.Ping, typeof(Ping) },
                { MessageType.Pong, typeof(Pong) },
                { MessageType.GetBlockHashes, typeof(GetBlockHashes) },
                { MessageType.BlockHashes, typeof(BlockHashes) },
                { MessageType.TxIds, typeof(TxIds) },
                { MessageType.GetBlocks, typeof(GetBlocks) },
                { MessageType.GetTxs, typeof(GetTxs) },
                { MessageType.Blocks, typeof(Blocks) },
                { MessageType.Tx, typeof(Tx) },
                { MessageType.FindNeighbors, typeof(FindNeighbors) },
                { MessageType.Neighbors, typeof(Neighbors) },
                { MessageType.GetRecentStates, typeof(GetRecentStates) },
                { MessageType.RecentStates, typeof(RecentStates) },
                { MessageType.BlockHeaderMessage, typeof(BlockHeaderMessage) },
                { MessageType.GetChainStatus, typeof(GetChainStatus) },
                { MessageType.ChainStatus, typeof(ChainStatus) },
                { MessageType.GetBlockStates, typeof(GetBlockStates) },
                { MessageType.BlockStates, typeof(BlockStates) },
                { MessageType.DifferentVersion, typeof(DifferentVersion) },
            };

            if (!types.TryGetValue(rawType, out Type type))
            {
                throw new InvalidMessageException(
                          $"Can't determine NetMQMessage. [type: {rawType}]");
            }

            var message = (Message)Activator.CreateInstance(
                type, new[] { body });

            message.Version = remoteVersion;
            message.Remote  = remotePeer;

            if (!message.Remote.PublicKey.Verify(body.ToByteArray(), signature))
            {
                throw new InvalidMessageException(
                          "The message signature is invalid"
                          );
            }

            if (!reply)
            {
                message.Identity = raw[0].Buffer.ToArray();
            }

            return(message);
        }
Esempio n. 7
0
        public void FromToken()
        {
            Assert.Equal(
                ValidClaimFixture,
                AppProtocolVersion.FromToken(
                    "1/271e00B29aeB93B2F4e30ECbebA4f72ac02f72b4/" +
                    "MEUCIQCJlZxZJYNOvEVZ15vKgkppIOUY8MWt4rmjo7Mpu6M92AIgHcuIoTo8GS3hnjn2WAXUBr+y" +
                    "k9FkhXWoosufldmQuVE="
                    )
                );
            Assert.Equal(
                ValidClaimWExtraFixture,
                AppProtocolVersion.FromToken(
                    "123/271e00B29aeB93B2F4e30ECbebA4f72ac02f72b4/" +
                    "MEQCIAhd1E0voVfgAcpvypiNeh0TdMvJJso7w98UPTfirQSIAiAW1K5yQjFj6XOZUAu5GUmh8rtj" +
                    "IJlad9IV.b1ZmexcUQ==/" +
                    "dTM6Zm9v"
                    )
                );

            Assert.Throws <ArgumentNullException>(() => AppProtocolVersion.FromToken(null));

            // No first delimiter
            Assert.Throws <FormatException>(() => AppProtocolVersion.FromToken("123"));

            // No second delimiter
            Assert.Throws <FormatException>(() =>
                                            AppProtocolVersion.FromToken("123/271e00B29aeB93B2F4e30ECbebA4f72ac02f72b4")
                                            );

            // A version is not an integer
            Assert.Throws <FormatException>(() =>
                                            AppProtocolVersion.FromToken(
                                                "INCORRECT/271e00B29aeB93B2F4e30ECbebA4f72ac02f72b4/" +
                                                "MEUCIQCJlZxZJYNOvEVZ15vKgkppIOUY8MWt4rmjo7Mpu6M92AIgHcuIoTo8GS3hnjn2WAXUBr+y" +
                                                "k9FkhXWoosufldmQuVE="
                                                )
                                            );

            // A signer address is incorrect
            Assert.Throws <FormatException>(() =>
                                            AppProtocolVersion.FromToken(
                                                "123/INCORRECT/" +
                                                "MEUCIQCJlZxZJYNOvEVZ15vKgkppIOUY8MWt4rmjo7Mpu6M92AIgHcuIoTo8GS3hnjn2WAXUBr+y" +
                                                "k9FkhXWoosufldmQuVE="
                                                )
                                            );

            // A signature is not a valid base64 string
            Assert.Throws <FormatException>(() =>
                                            AppProtocolVersion.FromToken(
                                                "123/271e00B29aeB93B2F4e30ECbebA4f72ac02f72b4/_INCORRECT_"
                                                )
                                            );

            // An extra data is not a valid base64 string
            Assert.Throws <FormatException>(() =>
                                            AppProtocolVersion.FromToken(
                                                "123/271e00B29aeB93B2F4e30ECbebA4f72ac02f72b4/" +
                                                "MEQCIAhd1E0voVfgAcpvypiNeh0TdMvJJso7w98UPTfirQSIAiAW1K5yQjFj6XOZUAu5GUmh8rtj" +
                                                "IJlad9IV.b1ZmexcUQ==/" +
                                                "_INCORRECT_"
                                                )
                                            );
        }
Esempio n. 8
0
        public Message Decode(
            NetMQMessage encoded,
            bool reply,
            Action <byte[], Peer, AppProtocolVersion> appProtocolVersionValidator,
            TimeSpan?lifetime)
        {
            if (encoded.FrameCount == 0)
            {
                throw new ArgumentException("Can't parse empty NetMQMessage.");
            }

            // (reply == true)            [version, peer, timestamp, type, sign, frames...]
            // (reply == false) [identity, version, peer, timestamp, type, sign, frames...]
            NetMQFrame[] remains = reply ? encoded.ToArray() : encoded.Skip(1).ToArray();

            var versionToken = remains[(int)Message.MessageFrame.Version].ConvertToString();

            AppProtocolVersion remoteVersion = AppProtocolVersion.FromToken(versionToken);
            Peer remotePeer;
            var  dictionary =
                (Bencodex.Types.Dictionary)_codec.Decode(
                    remains[(int)Message.MessageFrame.Peer].ToByteArray());

            try
            {
                remotePeer = new BoundPeer(dictionary);
            }
            catch (KeyNotFoundException)
            {
                remotePeer = new Peer(dictionary);
            }

            appProtocolVersionValidator(
                reply ? new byte[] { } : encoded[0].ToByteArray(),
                remotePeer,
                remoteVersion);

            var type =
                (Message.MessageType)remains[(int)Message.MessageFrame.Type].ConvertToInt32();
            var ticks     = remains[(int)Message.MessageFrame.Timestamp].ConvertToInt64();
            var timestamp = new DateTimeOffset(ticks, TimeSpan.Zero);

            var currentTime = DateTimeOffset.UtcNow;

            if (!(lifetime is null) &&
                (currentTime < timestamp || timestamp + lifetime < currentTime))
            {
                var msg = $"Received message is invalid, created at " +
                          $"{timestamp.ToString(TimestampFormat, CultureInfo.InvariantCulture)} " +
                          $"but designated lifetime is {lifetime} and the current datetime " +
                          $"offset is " +
                          $"{currentTime.ToString(TimestampFormat, CultureInfo.InvariantCulture)}.";
                throw new InvalidTimestampException(msg, timestamp, lifetime.Value, currentTime);
            }

            byte[] signature = remains[(int)Message.MessageFrame.Sign].ToByteArray();

            NetMQFrame[] body = remains.Skip(Message.CommonFrames)
                                .ToArray();

            Message message = CreateMessage(
                type,
                body.Select(frame => frame.ToByteArray()).ToArray());

            message.Version   = remoteVersion;
            message.Remote    = remotePeer;
            message.Timestamp = timestamp;

            var headerWithoutSign = new[]
            {
                remains[(int)Message.MessageFrame.Version],
                remains[(int)Message.MessageFrame.Peer],
                remains[(int)Message.MessageFrame.Timestamp],
                remains[(int)Message.MessageFrame.Type],
            };

            var messageForVerify = headerWithoutSign.Concat(body).ToArray();

            if (!remotePeer.PublicKey.Verify(messageForVerify.ToByteArray(), signature))
            {
                throw new InvalidMessageException("The message signature is invalid", message);
            }

            if (!reply)
            {
                message.Identity = encoded[0].Buffer.ToArray();
            }

            return(message);
        }
        GenerateLibplanetNodeServiceProperties(
            string?appProtocolVersionToken = null,
            string?genesisBlockPath        = null,
            string?swarmHost             = null,
            ushort?swarmPort             = null,
            string?swarmPrivateKeyString = null,
            int minimumDifficulty        = 5000000,
            string?storeType             = null,
            string?storePath             = null,
            int storeStateCacheSize      = 100,
            string[]?iceServerStrings    = null,
            string[]?peerStrings         = null,
            string[]?trustedAppProtocolVersionSigners = null,
            bool noMiner                     = false,
            bool render                      = false,
            int workers                      = 5,
            int confirmations                = 0,
            int maximumTransactions          = 100,
            int messageTimeout               = 60,
            int tipTimeout                   = 60,
            int demandBuffer                 = 1150,
            string[]?staticPeerStrings       = null,
            bool preload                     = true,
            int minimumBroadcastTarget       = 10,
            int bucketSize                   = 16,
            string chainTipStaleBehaviorType = "reboot")
        {
            var swarmPrivateKey = string.IsNullOrEmpty(swarmPrivateKeyString)
                ? new PrivateKey()
                : new PrivateKey(ByteUtil.ParseHex(swarmPrivateKeyString));

            peerStrings ??= Array.Empty <string>();
            iceServerStrings ??= Array.Empty <string>();
            staticPeerStrings ??= Array.Empty <string>();

            var iceServers  = iceServerStrings.Select(PropertyParser.ParseIceServer).ToImmutableArray();
            var peers       = peerStrings.Select(PropertyParser.ParsePeer).ToImmutableArray();
            var staticPeers = staticPeerStrings.Select(PropertyParser.ParsePeer).ToImmutableHashSet();

            return(new LibplanetNodeServiceProperties <NineChroniclesActionType>
            {
                Host = swarmHost,
                Port = swarmPort,
                SwarmPrivateKey = swarmPrivateKey,
                AppProtocolVersion = AppProtocolVersion.FromToken(appProtocolVersionToken),
                TrustedAppProtocolVersionSigners = trustedAppProtocolVersionSigners
                                                   ?.Select(s => new PublicKey(ByteUtil.ParseHex(s)))
                                                   ?.ToHashSet(),
                GenesisBlockPath = genesisBlockPath,
                NoMiner = noMiner,
                IceServers = iceServers,
                Peers = peers,
                StoreType = storeType,
                StorePath = storePath,
                StoreStatesCacheSize = storeStateCacheSize,
                MinimumDifficulty = minimumDifficulty,
                Render = render,
                Workers = workers,
                Confirmations = Math.Max(confirmations, 0),
                MaximumTransactions = maximumTransactions,
                MessageTimeout = TimeSpan.FromSeconds(messageTimeout),
                TipTimeout = TimeSpan.FromSeconds(tipTimeout),
                DemandBuffer = demandBuffer,
                StaticPeers = staticPeers,
                Preload = preload,
                MinimumBroadcastTarget = minimumBroadcastTarget,
                BucketSize = bucketSize,
                ChainTipStaleBehavior = chainTipStaleBehaviorType,
            });
        }
Esempio n. 10
0
        public static async Task Main(string[] args)
        {
            Options options = Options.Parse(args, Console.Error);

            var loggerConfig = new LoggerConfiguration();

            loggerConfig = options.Debug
                ? loggerConfig.MinimumLevel.Debug()
                : loggerConfig.MinimumLevel.Information();
            loggerConfig = loggerConfig
                           .MinimumLevel.Override("Microsoft", LogEventLevel.Information)
                           .Enrich.FromLogContext()
                           .WriteTo.Console();
            Log.Logger = loggerConfig.CreateLogger();

            try
            {
                var            privateKey = options.PrivateKey ?? new PrivateKey();
                RoutingTable   table      = new RoutingTable(privateKey.ToAddress());
                NetMQTransport transport  = new NetMQTransport(
                    table,
                    privateKey,
                    AppProtocolVersion.FromToken(options.AppProtocolVersionToken),
                    null,
                    options.Workers,
                    options.Host,
                    options.Port,
                    new[] { options.IceServer },
                    null);
                KademliaProtocol peerDiscovery = new KademliaProtocol(
                    table,
                    transport,
                    privateKey.ToAddress());
                Startup.TableSingleton = table;

                IWebHost webHost = WebHost.CreateDefaultBuilder()
                                   .UseStartup <SeedStartup <Startup> >()
                                   .UseSerilog()
                                   .UseUrls($"http://{options.GraphQLHost}:{options.GraphQLPort}/")
                                   .Build();

                using (var cts = new CancellationTokenSource())
                {
                    Console.CancelKeyPress += (sender, eventArgs) =>
                    {
                        eventArgs.Cancel = true;
                        cts.Cancel();
                    };

                    try
                    {
                        var tasks = new List <Task>
                        {
                            webHost.RunAsync(cts.Token),
                            StartTransportAsync(transport, cts.Token),
                            RefreshTableAsync(peerDiscovery, cts.Token),
                            RebuildConnectionAsync(peerDiscovery, cts.Token),
                        };
                        if (!(options.Peers is null) && options.Peers.Any())
                        {
                            tasks.Add(CheckStaticPeersAsync(
                                          options.Peers,
                                          table,
                                          peerDiscovery,
                                          cts.Token));
                        }

                        await Task.WhenAll(tasks);
                    }
                    catch (OperationCanceledException)
                    {
                        await transport.StopAsync(TimeSpan.FromSeconds(1));
                    }
                }
            }
            catch (InvalidOptionValueException e)
            {
                string expectedValues = string.Join(", ", e.ExpectedValues);
                Console.Error.WriteLine($"Unexpected value given through '{e.OptionName}'\n"
                                        + $"  given value: {e.OptionValue}\n"
                                        + $"  expected values: {expectedValues}");
            }
        }
Esempio n. 11
0
        /// <inheritdoc/>
        public Message Decode(
            byte[] encoded,
            bool reply,
            Action <byte[], Peer, AppProtocolVersion> appProtocolVersionValidator)
        {
            if (encoded.Length == 0)
            {
                throw new ArgumentException("Can't parse empty byte array.");
            }

            using var stream = new MemoryStream(encoded);
            var buffer = new byte[sizeof(int)];

            stream.Read(buffer, 0, sizeof(int));
            int frameCount = BitConverter.ToInt32(buffer, 0);
            var frames     = new List <byte[]>();

            for (var i = 0; i < frameCount; i++)
            {
                buffer = new byte[sizeof(int)];
                stream.Read(buffer, 0, sizeof(int));
                int frameSize = BitConverter.ToInt32(buffer, 0);
                buffer = new byte[frameSize];
                stream.Read(buffer, 0, frameSize);
                frames.Add(buffer);
            }

            // (reply == true)            [version, type, peer, timestamp, sign, frames...]
            // (reply == false) [identity, version, type, peer, timestamp, sign, frames...]
            List <byte[]> remains = reply ? frames : frames.Skip(1).ToList();

            var versionToken = Encoding.ASCII.GetString(remains[(int)Message.MessageFrame.Version]);

            AppProtocolVersion remoteVersion = AppProtocolVersion.FromToken(versionToken);
            Peer remotePeer;
            var  dictionary =
                (Bencodex.Types.Dictionary)_codec.Decode(remains[(int)Message.MessageFrame.Peer]);

            try
            {
                remotePeer = new BoundPeer(dictionary);
            }
            catch (KeyNotFoundException)
            {
                remotePeer = new Peer(dictionary);
            }

            appProtocolVersionValidator(
                reply ? new byte[] { } : frames[0],
                remotePeer,
                remoteVersion);

            var type =
                (Message.MessageType)BitConverter.ToInt32(
                    remains[(int)Message.MessageFrame.Type],
                    0);
            long ticks     = BitConverter.ToInt64(remains[(int)Message.MessageFrame.Timestamp], 0);
            var  timestamp = new DateTimeOffset(ticks, TimeSpan.Zero);

            var currentTime = DateTimeOffset.UtcNow;

            if (_messageTimestampBuffer is TimeSpan timestampBuffer &&
                (currentTime - timestamp).Duration() > timestampBuffer)
            {
                var msg = $"Received message is invalid, created at " +
                          $"{timestamp.ToString(TimestampFormat, CultureInfo.InvariantCulture)} " +
                          $"but designated lifetime is {timestampBuffer} and " +
                          $"the current datetime offset is " +
                          $"{currentTime.ToString(TimestampFormat, CultureInfo.InvariantCulture)}.";
                throw new InvalidMessageTimestampException(
                          msg, timestamp, _messageTimestampBuffer, currentTime);
            }

            byte[] signature = remains[(int)Message.MessageFrame.Sign];

            byte[][] body = remains.Skip(Message.CommonFrames).ToArray();

            Message message = CreateMessage(type, body);

            message.Version   = remoteVersion;
            message.Remote    = remotePeer;
            message.Timestamp = timestamp;

            var headerWithoutSign = new[]
            {
                remains[(int)Message.MessageFrame.Version],
                remains[(int)Message.MessageFrame.Type],
                remains[(int)Message.MessageFrame.Peer],
                remains[(int)Message.MessageFrame.Timestamp],
            };

            var messageForVerify = headerWithoutSign.Concat(body).Aggregate(
                new byte[] { },
                (arr, bytes) => arr.Concat(bytes).ToArray());

            if (!remotePeer.PublicKey.Verify(messageForVerify, signature))
            {
                throw new InvalidMessageSignatureException(
                          "The message signature is invalid", message);
            }

            if (!reply)
            {
                message.Identity = frames[0];
            }

            return(message);
        }
        public static async Task <AppProtocolVersion> QueryAppProtocolVersionTcp(
            this BoundPeer peer,
            TimeSpan?timeout = null,
            CancellationToken cancellationToken = default
            )
        {
            using var client = new TcpClient();
            try
            {
                await client.ConnectAsync(peer.EndPoint.Host, peer.EndPoint.Port);
            }
            catch (SocketException)
            {
                throw new TimeoutException("Cannot find peer.");
            }

            client.ReceiveTimeout = timeout?.Milliseconds ?? 0;
            using var stream      = client.GetStream();
            var key  = new PrivateKey();
            var ping = new Ping
            {
                Identity = Guid.NewGuid().ToByteArray(),
            };
            var messageCodec = new TcpMessageCodec();

            byte[] serialized = messageCodec.Encode(
                ping,
                key,
                peer,
                DateTimeOffset.UtcNow);
            int length = serialized.Length;
            var buffer = new byte[TcpTransport.MagicCookie.Length + sizeof(int) + length];

            TcpTransport.MagicCookie.CopyTo(buffer, 0);
            BitConverter.GetBytes(length).CopyTo(buffer, TcpTransport.MagicCookie.Length);
            serialized.CopyTo(buffer, TcpTransport.MagicCookie.Length + sizeof(int));
            await stream.WriteAsync(buffer, 0, buffer.Length, cancellationToken);

            buffer = new byte[1000000];
            int bytesRead = await stream.ReadAsync(buffer, 0, buffer.Length, cancellationToken);

            var magicCookieBuffer = new byte[TcpTransport.MagicCookie.Length];

            Array.Copy(buffer, 0, magicCookieBuffer, 0, TcpTransport.MagicCookie.Length);
            var sizeBuffer = new byte[sizeof(int)];

            Array.Copy(buffer, TcpTransport.MagicCookie.Length, sizeBuffer, 0, sizeof(int));
            length = BitConverter.ToInt32(sizeBuffer, 0);
            var contentBuffer = new byte[length];

            Array.Copy(
                buffer,
                TcpTransport.MagicCookie.Length + sizeof(int),
                contentBuffer,
                0,
                length);

            // length of identity
            Array.Copy(contentBuffer, 4, sizeBuffer, 0, 4);
            length = BitConverter.ToInt32(sizeBuffer, 0);

            // length of apv token
            Array.Copy(contentBuffer, 4 + 4 + length, sizeBuffer, 0, 4);
            int apvLength = BitConverter.ToInt32(sizeBuffer, 0);
            var apvBytes  = new byte[apvLength];

            Array.Copy(contentBuffer, 4 + 4 + length + 4, apvBytes, 0, apvLength);
            var                frame   = new NetMQFrame(apvBytes);
            string             token   = frame.ConvertToString();
            AppProtocolVersion version = AppProtocolVersion.FromToken(token);

            return(version);
        }
Esempio n. 13
0
        public void Analyze(
            [Argument(
                 Name = "APV-TOKEN",
                 Description = "An app protocol version token to analyze.  " +
                               "Read from the standard input if omitted."
                 )]
            string?token = null
            )
        {
            if (token is null)
            {
                token = Console.ReadLine();
            }

            AppProtocolVersion v;

            try
            {
                v = AppProtocolVersion.FromToken(token.Trim());
            }
            catch (FormatException e)
            {
                throw Utils.Error("Not a valid app protocol version token.  " + e);
            }

            var data = new List <(string, string)>
            {
                ("version", v.Version.ToString(CultureInfo.InvariantCulture)),
                ("signature", ByteUtil.Hex(v.Signature)),
                ("signer", v.Signer.ToString()),
            };

            if (v.Extra is IValue extra)
            {
                void TreeIntoTable(IValue tree, List <(string, string)> table, string key)
                {
                    switch (tree)
                    {
                    case Null _:
                        table.Add((key, "null"));
                        return;

                    case Bencodex.Types.Boolean b:
                        table.Add((key, b ? "true" : "false"));
                        return;

                    case Binary bin:
                        table.Add((key, ByteUtil.Hex(bin.Value)));
                        return;

                    case Text t:
                        table.Add((key, t.Value));
                        return;

                    case Bencodex.Types.Integer i:
                        table.Add((key, i.Value.ToString(CultureInfo.InvariantCulture)));
                        return;

                    case Bencodex.Types.List l:
                        int idx = 0;
                        foreach (IValue el in l)
                        {
                            TreeIntoTable(el, table, $"{key}[{idx}]");
                            idx++;
                        }

                        return;

                    case Bencodex.Types.Dictionary d:
                        foreach (KeyValuePair <IKey, IValue> kv in d)
                        {
                            string k = kv.Key switch
                            {
                                Binary bk => ByteUtil.Hex(bk),
                                Text txt => txt.Value,
                                _ => kv.Key.ToString() ?? string.Empty,
                            };
                            TreeIntoTable(kv.Value, table, $"{key}.{k}");
                        }

                        return;

                    default:
                        table.Add((key, tree.ToString() ?? string.Empty));
                        return;
                    }
                }

                TreeIntoTable(v.Extra, data, "extra");
            }

            Utils.PrintTable(("Field", "Value"), data);
        }
Esempio n. 14
0
        public static async Task Main(string[] args)
        {
            Options options = Options.Parse(args, Console.Error);

            var loggerConfig = new LoggerConfiguration();

            loggerConfig = options.Debug
                ? loggerConfig.MinimumLevel.Debug()
                : loggerConfig.MinimumLevel.Information();
            loggerConfig = loggerConfig
                           .MinimumLevel.Override("Microsoft", LogEventLevel.Information)
                           .Enrich.FromLogContext()
                           .WriteTo.Console();
            Log.Logger = loggerConfig.CreateLogger();

            try
            {
                IStore store = LoadStore(options);

                IBlockPolicy <AppAgnosticAction> policy = new BlockPolicy <AppAgnosticAction>(
                    null,
                    blockIntervalMilliseconds: options.BlockIntervalMilliseconds,
                    minimumDifficulty: options.MinimumDifficulty,
                    difficultyBoundDivisor: options.DifficultyBoundDivisor);
                var blockChain =
                    new BlockChain <AppAgnosticAction>(policy, store, options.GenesisBlock);
                Startup.BlockChainSingleton = blockChain;
                Startup.StoreSingleton      = store;

                IWebHost webHost = WebHost.CreateDefaultBuilder()
                                   .UseStartup <ExplorerStartup <AppAgnosticAction, Startup> >()
                                   .UseSerilog()
                                   .UseUrls($"http://{options.Host}:{options.Port}/")
                                   .Build();

                Swarm <AppAgnosticAction> swarm = null;
                if (options.Seeds.Any())
                {
                    string aggregatedSeedStrings =
                        options.SeedStrings.Aggregate(string.Empty, (s, s1) => s + s1);
                    Console.WriteLine(
                        $"Seeds are {aggregatedSeedStrings}");

                    // TODO: Take privateKey as a CLI option
                    // TODO: Take appProtocolVersion as a CLI option
                    // TODO: Take host as a CLI option
                    // TODO: Take listenPort as a CLI option
                    if (options.IceServer is null)
                    {
                        Console.Error.WriteLine(
                            "error: -s/--seed option requires -I/--ice-server as well."
                            );
                        Environment.Exit(1);
                        return;
                    }

                    Console.WriteLine("Creating Swarm.");

                    var privateKey = new PrivateKey();

                    // FIXME: The appProtocolVersion should be fixed properly.
                    swarm = new Swarm <AppAgnosticAction>(
                        blockChain,
                        privateKey,
                        options.AppProtocolVersionToken is string t
                            ? AppProtocolVersion.FromToken(t)
                            : default(AppProtocolVersion),
                        differentAppProtocolVersionEncountered: (p, pv, lv) => true,
                        iceServers: new[] { options.IceServer }
                        );
                }

                using (var cts = new CancellationTokenSource())
                    using (swarm)
                    {
                        Console.CancelKeyPress += (sender, eventArgs) =>
                        {
                            eventArgs.Cancel = true;
                            cts.Cancel();
                        };

                        try
                        {
                            await Task.WhenAll(
                                webHost.RunAsync(cts.Token),
                                StartSwarmAsync(swarm, options.Seeds, cts.Token)
                                );
                        }
                        catch (OperationCanceledException)
                        {
                            await swarm?.StopAsync(waitFor : TimeSpan.FromSeconds(1))
                            .ContinueWith(_ => NetMQConfig.Cleanup(false));
                        }
                    }
            }
            catch (InvalidOptionValueException e)
            {
                string expectedValues = string.Join(", ", e.ExpectedValues);
                Console.Error.WriteLine($"Unexpected value given through '{e.OptionName}'\n"
                                        + $"  given value: {e.OptionValue}\n"
                                        + $"  expected values: {expectedValues}");
            }
        }
Esempio n. 15
0
#pragma warning disable MEN003 // Method Main must be no longer than 120 lines
        public static async Task Main(string[] args)
        {
            Options options = Options.Parse(args, Console.Error);

            var loggerConfig = new LoggerConfiguration();

            switch (options.LogLevel)
            {
            case "error":
                loggerConfig = loggerConfig.MinimumLevel.Error();
                break;

            case "warning":
                loggerConfig = loggerConfig.MinimumLevel.Warning();
                break;

            case "information":
                loggerConfig = loggerConfig.MinimumLevel.Information();
                break;

            case "debug":
                loggerConfig = loggerConfig.MinimumLevel.Debug();
                break;

            case "verbose":
                loggerConfig = loggerConfig.MinimumLevel.Verbose();
                break;

            default:
                loggerConfig = loggerConfig.MinimumLevel.Information();
                break;
            }

            loggerConfig = loggerConfig
                           .MinimumLevel.Override("Microsoft", LogEventLevel.Information)
                           .Enrich.FromLogContext()
                           .WriteTo.Console();
            Log.Logger = loggerConfig.CreateLogger();

            if (options.IceServer is null && options.Host is null)
            {
                Log.Error(
                    "-h/--host is required if -I/--ice-server is not given."
                    );
                Environment.Exit(1);
                return;
            }

            if (!(options.IceServer is null || options.Host is null))
            {
                Log.Warning("-I/--ice-server will not work because -h/--host is given.");
            }

            try
            {
                var          privateKey = options.PrivateKey ?? new PrivateKey();
                RoutingTable table      = new RoutingTable(privateKey.ToAddress());
                ITransport   transport;
                switch (options.TransportType)
                {
                case "tcp":
                    transport = new TcpTransport(
                        privateKey,
                        AppProtocolVersion.FromToken(options.AppProtocolVersionToken),
                        null,
                        host: options.Host,
                        listenPort: options.Port,
                        iceServers: new[] { options.IceServer },
                        differentAppProtocolVersionEncountered: null);
                    break;

                case "netmq":
                    transport = new NetMQTransport(
                        privateKey,
                        AppProtocolVersion.FromToken(options.AppProtocolVersionToken),
                        null,
                        workers: options.Workers,
                        host: options.Host,
                        listenPort: options.Port,
                        iceServers: new[] { options.IceServer },
                        differentAppProtocolVersionEncountered: null);
                    break;

                default:
                    Log.Error(
                        "-t/--transport-type must be either \"tcp\" or \"netmq\".");
                    Environment.Exit(1);
                    return;
                }

                KademliaProtocol peerDiscovery = new KademliaProtocol(
                    table,
                    transport,
                    privateKey.ToAddress());
                Startup.TableSingleton = table;

                IWebHost webHost = WebHost.CreateDefaultBuilder()
                                   .UseStartup <SeedStartup <Startup> >()
                                   .UseSerilog()
                                   .UseUrls($"http://{options.GraphQLHost}:{options.GraphQLPort}/")
                                   .Build();

                using (var cts = new CancellationTokenSource())
                {
                    Console.CancelKeyPress += (sender, eventArgs) =>
                    {
                        eventArgs.Cancel = true;
                        cts.Cancel();
                    };

                    try
                    {
                        var tasks = new List <Task>
                        {
                            webHost.RunAsync(cts.Token),
                            StartTransportAsync(transport, cts.Token),
                            RefreshTableAsync(peerDiscovery, cts.Token),
                            RebuildConnectionAsync(peerDiscovery, cts.Token),
                        };
                        if (!(options.Peers is null) && options.Peers.Any())
                        {
                            tasks.Add(CheckStaticPeersAsync(
                                          options.Peers,
                                          table,
                                          peerDiscovery,
                                          cts.Token));
                        }

                        await Task.WhenAll(tasks);
                    }
                    catch (OperationCanceledException)
                    {
                        await transport.StopAsync(TimeSpan.FromSeconds(1));
                    }
                }
            }
            catch (InvalidOptionValueException e)
            {
                string expectedValues = string.Join(", ", e.ExpectedValues);
                Console.Error.WriteLine($"Unexpected value given through '{e.OptionName}'\n"
                                        + $"  given value: {e.OptionValue}\n"
                                        + $"  expected values: {expectedValues}");
            }
        }
Esempio n. 16
0
        public async Task Run(
            [Option(
                 "store-path",
                 new[] { 'P' },
                 Description = @"The path of the blockchain store. If omitted (default)
in memory version is used.")]
            string storePath,
            [Option(
                 "store-type",
                 new[] { 'T' },
                 Description = @"The type of the blockchain store. If omitted (default)
in DefaultStore is used.")]
            string storeType,
            [Option(
                 "genesis-block",
                 new[] { 'G' },
                 Description = "The path of the genesis block. It should be absolute or http url.")]
            string genesisBlockPath,
            [Option("debug", new[] { 'd' }, Description = "Print logs for debugging as well.")]
            bool debug = false,
            [Option("host", new[] { 'H' }, Description = "The host address to listen.")]
            string host = "0.0.0.0",
            [Option("port", new[] { 'p' }, Description = "The port number to listen.")]
            int port = 5000,
            [Option(
                 "block-interval",
                 new[] { 'i' },
                 Description = @"An appropriate interval in milliseconds between
consecutive blocks.")]
            int blockIntervalMilliseconds = 5000,
            [Option(
                 "minimum-difficulty",
                 new[] { 'm' },
                 Description = "Allowed minimum difficulty for mining blocks.")]
            long minimumDifficulty = 1024L,
            [Option(
                 "difficulty-bound-divisor",
                 new[] { 'D' },
                 Description = "A bound divisor to determine precision of block difficulties.")]
            int difficultyBoundDivisor = 128,
            [Option(
                 "workers",
                 new[] { 'W' },
                 Description = "The number of swarm workers.")]
            int workers = 50,
            [Option(
                 "app-protocol-version",
                 new[] { 'V' },
                 Description = "An app protocol version token.")]
            string appProtocolVersionToken = null,
            [Option(
                 "mysql-server",
                 Description = "A hostname of MySQL server.")]
            string mysqlServer = null,
            [Option(
                 "mysql-port",
                 Description = "A port of MySQL server.")]
            uint?mysqlPort = null,
            [Option(
                 "mysql-username",
                 Description = "The name of MySQL user.")]
            string mysqlUsername = null,
            [Option(
                 "mysql-password",
                 Description = "The password of MySQL user.")]
            string mysqlPassword = null,
            [Option(
                 "mysql-database",
                 Description = "The name of MySQL database to use.")]
            string mysqlDatabase = null,
            [Option(
                 "max-transactions-per-block",
                 Description = @"The number of maximum transactions able to be included
in a block.")]
            int maxTransactionsPerBlock = 100,
            [Option(
                 "max-block-bytes",
                 Description = @"The number of maximum bytes size of blocks except
for genesis block.")]
            int maxBlockBytes = 100 * 1024,
            [Option(
                 "max-genesis-bytes",
                 Description = "The number of maximum bytes size of the genesis block.")]
            int maxGenesisBytes = 1024 * 1024,
            [Option(
                 "seed",
                 new[] { 's' },
                 Description = @"Seed nodes to join to the network as a node. The format of each
seed is a comma-separated triple of a peer's hexadecimal public key, host, and port number.
E.g., `02ed49dbe0f2c34d9dff8335d6dd9097f7a3ef17dfb5f048382eebc7f451a50aa1,example.com,31234'.
If omitted (default) explorer only the local blockchain store.")]
            string[] seedStrings = null,
            [Option(
                 "ice-server",
                 new[] { 'I' },
                 Description = "URL to ICE server (TURN/STUN) to work around NAT.")]
            string iceServerUrl = null
            )
        {
            Options options = new Options(
                debug,
                host,
                port,
                blockIntervalMilliseconds,
                minimumDifficulty,
                difficultyBoundDivisor,
                workers,
                appProtocolVersionToken,
                mysqlServer,
                mysqlPort,
                mysqlUsername,
                mysqlPassword,
                mysqlDatabase,
                maxTransactionsPerBlock,
                maxBlockBytes,
                maxGenesisBytes,
                seedStrings,
                iceServerUrl,
                storePath,
                storeType,
                genesisBlockPath);

            var loggerConfig = new LoggerConfiguration();

            loggerConfig = options.Debug
                ? loggerConfig.MinimumLevel.Debug()
                : loggerConfig.MinimumLevel.Information();
            loggerConfig = loggerConfig
                           .MinimumLevel.Override("Microsoft", LogEventLevel.Information)
                           .Enrich.FromLogContext()
                           .WriteTo.Console();
            Log.Logger = loggerConfig.CreateLogger();

            try
            {
                IRichStore  store      = LoadStore(options);
                IStateStore stateStore = new NoOpStateStore();

                IBlockPolicy <NullAction> policy =
                    new DumbBlockPolicy(LoadBlockPolicy <NullAction>(options));
                IStagePolicy <NullAction> stagePolicy =
                    new VolatileStagePolicy <NullAction>();
                var blockChain =
                    new BlockChain <NullAction>(
                        policy,
                        stagePolicy,
                        store,
                        stateStore,
                        options.GetGenesisBlock(policy));
                Startup.PreloadedSingleton  = false;
                Startup.BlockChainSingleton = blockChain;
                Startup.StoreSingleton      = store;

                IWebHost webHost = WebHost.CreateDefaultBuilder()
                                   .UseStartup <ExplorerStartup <NullAction, Startup> >()
                                   .UseSerilog()
                                   .UseUrls($"http://{options.Host}:{options.Port}/")
                                   .Build();

                Swarm <NullAction> swarm = null;
                if (!(options.Seeds is null))
                {
                    string aggregatedSeedStrings =
                        options.SeedStrings.Aggregate(string.Empty, (s, s1) => s + s1);
                    Console.Error.WriteLine(
                        $"Seeds are {aggregatedSeedStrings}");

                    // TODO: Take privateKey as a CLI option
                    // TODO: Take appProtocolVersion as a CLI option
                    // TODO: Take host as a CLI option
                    // TODO: Take listenPort as a CLI option
                    if (options.IceServer is null)
                    {
                        Console.Error.WriteLine(
                            "error: -s/--seed option requires -I/--ice-server as well."
                            );
                        Environment.Exit(1);
                        return;
                    }

                    Console.Error.WriteLine("Creating Swarm.");

                    var privateKey = new PrivateKey();

                    // FIXME: The appProtocolVersion should be fixed properly.
                    var swarmOptions = new SwarmOptions
                    {
                        MaxTimeout = TimeSpan.FromSeconds(10),
                    };

                    swarm = new Swarm <NullAction>(
                        blockChain,
                        privateKey,
                        options.AppProtocolVersionToken is string t
                            ? AppProtocolVersion.FromToken(t)
                            : default(AppProtocolVersion),
                        differentAppProtocolVersionEncountered: (p, pv, lv) => true,
                        workers: options.Workers,
                        iceServers: new[] { options.IceServer },
                        options: swarmOptions
                        );
                }

                using (var cts = new CancellationTokenSource())
                    using (swarm)
                    {
                        Console.CancelKeyPress += (sender, eventArgs) =>
                        {
                            eventArgs.Cancel = true;
                            cts.Cancel();
                        };

                        try
                        {
                            await Task.WhenAll(
                                webHost.RunAsync(cts.Token),
                                StartSwarmAsync(swarm, options.Seeds, cts.Token)
                                );
                        }
                        catch (OperationCanceledException)
                        {
                            await swarm?.StopAsync(waitFor : TimeSpan.FromSeconds(1))
                            .ContinueWith(_ => NetMQConfig.Cleanup(false));
                        }
                    }
            }
            catch (InvalidOptionValueException e)
            {
                string expectedValues = string.Join(", ", e.ExpectedValues);
                Console.Error.WriteLine($"Unexpected value given through '{e.OptionName}'\n"
                                        + $"  given value: {e.OptionValue}\n"
                                        + $"  expected values: {expectedValues}");
            }
        }
Esempio n. 17
0
        /// <inheritdoc/>
        public Message Decode(
            NetMQMessage encoded,
            bool reply)
        {
            if (encoded.FrameCount == 0)
            {
                throw new ArgumentException("Can't parse empty NetMQMessage.");
            }

            // (reply == true)            [version, type, peer, timestamp, sign, frames...]
            // (reply == false) [identity, version, type, peer, timestamp, sign, frames...]
            NetMQFrame[] remains = reply ? encoded.ToArray() : encoded.Skip(1).ToArray();

            var versionToken = remains[(int)Message.MessageFrame.Version].ConvertToString();

            AppProtocolVersion remoteVersion = AppProtocolVersion.FromToken(versionToken);
            Peer remotePeer;
            var  dictionary =
                (Bencodex.Types.Dictionary)_codec.Decode(
                    remains[(int)Message.MessageFrame.Peer].ToByteArray());

            try
            {
                remotePeer = new BoundPeer(dictionary);
            }
            catch (KeyNotFoundException)
            {
                remotePeer = new Peer(dictionary);
            }

            _messageValidator.ValidateAppProtocolVersion(
                remotePeer,
                reply ? new byte[] { } : encoded[0].ToByteArray(),
                remoteVersion);

            var type =
                (Message.MessageType)remains[(int)Message.MessageFrame.Type].ConvertToInt32();
            var ticks       = remains[(int)Message.MessageFrame.Timestamp].ConvertToInt64();
            var timestamp   = new DateTimeOffset(ticks, TimeSpan.Zero);
            var currentTime = DateTimeOffset.UtcNow;

            _messageValidator.ValidateTimestamp(remotePeer, currentTime, timestamp);

            byte[] signature = remains[(int)Message.MessageFrame.Sign].ToByteArray();

            NetMQFrame[] body = remains.Skip(Message.CommonFrames)
                                .ToArray();

            Message message = CreateMessage(
                type,
                body.Select(frame => frame.ToByteArray()).ToArray());

            message.Version   = remoteVersion;
            message.Remote    = remotePeer;
            message.Timestamp = timestamp;

            var headerWithoutSign = new[]
            {
                remains[(int)Message.MessageFrame.Version],
                remains[(int)Message.MessageFrame.Type],
                remains[(int)Message.MessageFrame.Peer],
                remains[(int)Message.MessageFrame.Timestamp],
            };

            var messageToVerify = headerWithoutSign.Concat(body).ToByteArray();

            if (!remotePeer.PublicKey.Verify(messageToVerify, signature))
            {
                throw new InvalidMessageSignatureException(
                          "The signature of an encoded message is invalid.",
                          remotePeer,
                          remotePeer.PublicKey,
                          messageToVerify,
                          signature);
            }

            if (!reply)
            {
                message.Identity = encoded[0].Buffer.ToArray();
            }

            return(message);
        }