Example #1
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 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}");
            }
        }