Exemple #1
0
 public ClientHub(IOptions <SwarmOptions> options,
                  ISwarmStore store, ILoggerFactory loggerFactory)
 {
     _options = options.Value;
     _logger  = loggerFactory.CreateLogger <ClientHub>();
     _store   = store;
 }
Exemple #2
0
        private Swarm <DumbAction> CreateSwarm(
            PrivateKey privateKey = null,
            AppProtocolVersion?appProtocolVersion = null,
            int tableSize                      = Kademlia.TableSize,
            int bucketSize                     = Kademlia.BucketSize,
            string host                        = null,
            int?listenPort                     = null,
            DateTimeOffset?createdAt           = null,
            IEnumerable <IceServer> iceServers = null,
            DifferentAppProtocolVersionEncountered differentAppProtocolVersionEncountered = null,
            IEnumerable <PublicKey> trustedAppProtocolVersionSigners = null,
            SwarmOptions options = null)
        {
            var policy     = new BlockPolicy <DumbAction>(new MinerReward(1));
            var fx         = new DefaultStoreFixture(memory: true, blockAction: policy.BlockAction);
            var blockchain = TestUtils.MakeBlockChain(policy, fx.Store, fx.StateStore);

            return(CreateSwarm(
                       blockchain,
                       privateKey,
                       appProtocolVersion,
                       tableSize,
                       bucketSize,
                       host,
                       listenPort,
                       createdAt,
                       iceServers,
                       differentAppProtocolVersionEncountered,
                       trustedAppProtocolVersionSigners,
                       options));
        }
        private Swarm <DumbAction> CreateSwarm(
            PrivateKey privateKey = null,
            AppProtocolVersion?appProtocolVersion = null,
            string host    = null,
            int?listenPort = null,
            IEnumerable <IceServer> iceServers = null,
            DifferentAppProtocolVersionEncountered differentAppProtocolVersionEncountered = null,
            IEnumerable <PublicKey> trustedAppProtocolVersionSigners = null,
            SwarmOptions options             = null,
            IBlockPolicy <DumbAction> policy = null,
            Block <DumbAction> genesis       = null)
        {
            policy = policy ?? new BlockPolicy <DumbAction>(new MinerReward(1));
            var fx         = new MemoryStoreFixture(policy.BlockAction);
            var blockchain = MakeBlockChain(
                policy,
                fx.Store,
                fx.StateStore,
                genesisBlock: genesis
                );

            return(CreateSwarm(
                       blockchain,
                       privateKey,
                       appProtocolVersion,
                       host,
                       listenPort,
                       iceServers,
                       differentAppProtocolVersionEncountered,
                       trustedAppProtocolVersionSigners,
                       options));
        }
        public void Defaults()
        {
            var options = new SwarmOptions();

            Assert.Null(options.PrivateNetworkKey);
            Assert.AreEqual(8, options.MinConnections);
        }
        private Swarm <T> CreateSwarm <T>(
            BlockChain <T> blockChain,
            PrivateKey privateKey = null,
            AppProtocolVersion?appProtocolVersion = null,
            string host    = null,
            int?listenPort = null,
            IEnumerable <IceServer> iceServers = null,
            DifferentAppProtocolVersionEncountered differentAppProtocolVersionEncountered = null,
            IEnumerable <PublicKey> trustedAppProtocolVersionSigners = null,
            SwarmOptions options = null
            )
            where T : IAction, new()
        {
            if (host is null && !(iceServers?.Any() ?? false))
            {
                host = IPAddress.Loopback.ToString();
            }

            options ??= new SwarmOptions();
            string type = Environment.GetEnvironmentVariable("TRANSPORT_TYPE");

            _logger.Debug("Transport type: {Type}", type);
            switch (type)
            {
            case "tcp":
                options.Type = SwarmOptions.TransportType.TcpTransport;
                break;

            case "netmq":
                options.Type = SwarmOptions.TransportType.NetMQTransport;
                break;
            }

            var swarm = new Swarm <T>(
                blockChain,
                privateKey ?? new PrivateKey(),
                appProtocolVersion ?? DefaultAppProtocolVersion,
                5,
                host,
                listenPort,
                iceServers,
                differentAppProtocolVersionEncountered,
                trustedAppProtocolVersionSigners,
                options);

            _finalizers.Add(async() =>
            {
                try
                {
                    await StopAsync(swarm);
                    swarm.Dispose();
                }
                catch (ObjectDisposedException)
                {
                    _logger.Debug("Swarm {Swarm} is already disposed.", swarm);
                }
            });
            return(swarm);
        }
Exemple #6
0
 public SwarmCluster(IOptions <SwarmOptions> options, ISharding sharding, IClientStore clientStore,
                     ILoggerFactory loggerFactory)
 {
     _options     = options.Value;
     _sharding    = sharding;
     _clientStore = clientStore;
     _logger      = loggerFactory.CreateLogger <SwarmCluster>();
 }
Exemple #7
0
 public JobService(ISharding sharding, ISchedCache schedulerCache, ILoggerFactory loggerFactory,
                   ISwarmStore store,
                   IHubContext <ClientHub> hubContext,
                   IOptions <SwarmOptions> options)
 {
     _sharding   = sharding;
     _logger     = loggerFactory.CreateLogger <JobService>();
     _store      = store;
     _hubContext = hubContext;
     _schedCache = schedulerCache;
     _options    = options.Value;
 }
        public async Task BlockDownloadTimeout()
        {
            var options = new SwarmOptions
            {
                BlockDownloadTimeout = TimeSpan.FromMilliseconds(1),
            };

            var minerKey = new PrivateKey();
            Swarm <DumbAction> minerSwarm    = CreateSwarm(minerKey);
            Swarm <DumbAction> receiverSwarm = CreateSwarm(options: options);

            foreach (var unused in Enumerable.Range(0, 10))
            {
                await minerSwarm.BlockChain.MineBlock(minerKey);
            }

            try
            {
                await StartAsync(minerSwarm);

                await receiverSwarm.AddPeersAsync(new[] { minerSwarm.AsPeer }, null);

                Task waitTask = receiverSwarm.BlockDownloadStarted.WaitAsync();

                Task  preloadTask = receiverSwarm.PreloadAsync(TimeSpan.FromSeconds(15));
                await waitTask;
                await StopAsync(minerSwarm);

                Exception thrown = null;

                try
                {
                    await preloadTask;
                }
                catch (OperationCanceledException e)
                {
                    thrown = e;
                }

                Assert.True(
                    thrown is OperationCanceledException || thrown is TaskCanceledException,
                    $"The exception thrown is {thrown}"
                    );
            }
            finally
            {
                await StopAsync(minerSwarm);
            }
        }
Exemple #9
0
        private Swarm <T> CreateSwarm <T>(
            BlockChain <T> blockChain,
            PrivateKey privateKey = null,
            AppProtocolVersion?appProtocolVersion = null,
            int tableSize                      = Kademlia.TableSize,
            int bucketSize                     = Kademlia.BucketSize,
            string host                        = null,
            int?listenPort                     = null,
            DateTimeOffset?createdAt           = null,
            IEnumerable <IceServer> iceServers = null,
            DifferentAppProtocolVersionEncountered differentAppProtocolVersionEncountered = null,
            IEnumerable <PublicKey> trustedAppProtocolVersionSigners = null,
            SwarmOptions options = null
            )
            where T : IAction, new()
        {
            if (host is null && !(iceServers?.Any() ?? false))
            {
                host = IPAddress.Loopback.ToString();
            }

            var swarm = new Swarm <T>(
                blockChain,
                privateKey ?? new PrivateKey(),
                appProtocolVersion ?? DefaultAppProtocolVersion,
                tableSize,
                bucketSize,
                5,
                host,
                listenPort,
                createdAt,
                iceServers,
                differentAppProtocolVersionEncountered,
                trustedAppProtocolVersionSigners,
                options);

            _finalizers.Add(async() =>
            {
                await StopAsync(swarm);
                swarm.Dispose();
            });
            return(swarm);
        }
        public static bool IsAccess(this HttpRequest request, SwarmOptions options)
        {
            if (options == null)
            {
                throw new ArgumentNullException(nameof(request));
            }
            if (options.AccessTokens == null || options.AccessTokens.Count == 0)
            {
                return(true);
            }

            if (request.Headers.ContainsKey(SwarmConts.AccessTokenHeader))
            {
                var token = request.Headers[SwarmConts.AccessTokenHeader].ToString();
                return(options.AccessTokens.Contains(token));
            }

            return(false);
        }
        public static Client GetClient(this HubCallerContext context, SwarmOptions options)
        {
            var name  = context.GetHttpRequest().Query["name"].FirstOrDefault();
            var group = context.GetHttpRequest().Query["group"].FirstOrDefault();
            var ip    = context.GetHttpRequest().Query["ip"].FirstOrDefault();

            group = string.IsNullOrWhiteSpace(group) ? SwarmConsts.DefaultGroup : group;
            var userId    = int.Parse(context.GetHttpRequest().Query["userId"].First());
            var os        = context.GetHttpRequest().Query["os"].FirstOrDefault();
            var coreCount = int.Parse(context.GetHttpRequest().Query["coreCount"].First());
            var memory    = int.Parse(context.GetHttpRequest().Query["memory"].First());

            //TODO: 数据验证

            return(new Client
            {
                Name = name, Group = group, Ip = ip, ConnectionId = context.ConnectionId, Memory = memory, Os = os,
                CoreCount = coreCount, SchedName = options.SchedName, SchedInstanceId = options.SchedInstanceId
            });
        }
        private Swarm <T> CreateSwarm <T>(
            BlockChain <T> blockChain,
            PrivateKey privateKey = null,
            AppProtocolVersion?appProtocolVersion = null,
            int?tableSize                      = null,
            int?bucketSize                     = null,
            string host                        = null,
            int?listenPort                     = null,
            DateTimeOffset?createdAt           = null,
            IEnumerable <IceServer> iceServers = null,
            DifferentAppProtocolVersionEncountered differentAppProtocolVersionEncountered = null,
            IEnumerable <PublicKey> trustedAppProtocolVersionSigners = null,
            SwarmOptions options = null
            )
            where T : IAction, new()
        {
            if (host is null && !(iceServers?.Any() ?? false))
            {
                host = IPAddress.Loopback.ToString();
            }

            return(new Swarm <T>(
                       blockChain,
                       privateKey ?? new PrivateKey(),
                       appProtocolVersion ?? DefaultAppProtocolVersion,
                       tableSize,
                       bucketSize,
                       5,
                       host,
                       listenPort,
                       createdAt,
                       iceServers,
                       differentAppProtocolVersionEncountered,
                       trustedAppProtocolVersionSigners,
                       options));
        }
Exemple #13
0
        public async Task QueryAppProtocolVersion(SwarmOptions.TransportType transportType)
        {
            var fx                 = new MemoryStoreFixture();
            var policy             = new BlockPolicy <DumbAction>();
            var blockchain         = MakeBlockChain(policy, fx.Store, fx.StateStore);
            var apvKey             = new PrivateKey();
            var swarmKey           = new PrivateKey();
            AppProtocolVersion apv = AppProtocolVersion.Sign(apvKey, 1);

            string host = IPAddress.Loopback.ToString();
            int    port = FreeTcpPort();

            var option = new SwarmOptions
            {
                Type = transportType,
            };

            using (var swarm = new Swarm <DumbAction>(
                       blockchain,
                       swarmKey,
                       apv,
                       host: host,
                       listenPort: port,
                       options: option))
            {
                var peer = new BoundPeer(swarmKey.PublicKey, new DnsEndPoint(host, port));
                // Before swarm starting...
                await Assert.ThrowsAsync <TimeoutException>(async() =>
                {
                    if (swarm.Transport is NetMQTransport)
                    {
                        peer.QueryAppProtocolVersionNetMQ(timeout: TimeSpan.FromSeconds(1));
                    }
                    else if (swarm.Transport is TcpTransport)
                    {
                        await peer.QueryAppProtocolVersionTcp(timeout: TimeSpan.FromSeconds(1));
                    }
                    else
                    {
                        throw new XunitException(
                            "Each type of transport must have corresponding test case.");
                    }
                });

                _ = swarm.StartAsync();
                try
                {
                    AppProtocolVersion receivedAPV = default;
                    if (swarm.Transport is NetMQTransport)
                    {
                        receivedAPV = peer.QueryAppProtocolVersionNetMQ();
                    }
                    else if (swarm.Transport is TcpTransport)
                    {
                        receivedAPV = await peer.QueryAppProtocolVersionTcp();
                    }
                    else
                    {
                        throw new XunitException(
                                  "Each type of transport must have corresponding test case.");
                    }

                    Assert.Equal(apv, receivedAPV);
                }
                finally
                {
                    await swarm.StopAsync();
                }
            }

            if (transportType == SwarmOptions.TransportType.NetMQTransport)
            {
                NetMQConfig.Cleanup(false);
            }
        }
 protected AbstractApiControllerBase(IOptions <SwarmOptions> options)
 {
     Options = options.Value;
 }
Exemple #15
0
 public DefaultSharding(ISwarmStore store, IOptions <SwarmOptions> options)
 {
     _store   = store;
     _options = options.Value;
 }
Exemple #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}");
            }
        }
 public SqlServerSwarmStore(IOptions <SwarmOptions> options)
 {
     _options = options.Value;
 }
Exemple #18
0
 public SqlServerSwarmStore(IOptions <SwarmOptions> options, ILoggerFactory loggerFactory)
 {
     _options = options.Value;
     _logger  = loggerFactory.CreateLogger <SqlServerSwarmStore>();
 }
        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
            {
                IRichStore store = LoadStore(options);

                var pendingTxs = store.IterateStagedTransactionIds()
                                 .ToImmutableHashSet();
                store.UnstageTransactionIds(pendingTxs);
                Log.Debug("Pending txs unstaged. [{PendingCount}]", pendingTxs.Count);

                IBlockPolicy <AppAgnosticAction> policy = new DumbBlockPolicy(
                    new BlockPolicy <AppAgnosticAction>(
                        null,
                        blockIntervalMilliseconds: options.BlockIntervalMilliseconds,
                        minimumDifficulty: options.MinimumDifficulty,
                        difficultyBoundDivisor: options.DifficultyBoundDivisor)
                    );
                var blockChain =
                    new BlockChain <AppAgnosticAction>(policy, store, store, options.GenesisBlock);
                Startup.PreloadedSingleton  = false;
                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.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 <AppAgnosticAction>(
                        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}");
            }
        }