/// <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, }); }
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}"); } }
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); }
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); }
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_" ) ); }
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, }); }
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}"); } }
/// <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); }
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); }
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}"); } }
#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}"); } }
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}"); } }
/// <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); }