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