diff --git a/.github/workflows/deploy_gh_pages.yaml b/.github/workflows/deploy_gh_pages.yaml index 85e36ddc8..2212312e7 100644 --- a/.github/workflows/deploy_gh_pages.yaml +++ b/.github/workflows/deploy_gh_pages.yaml @@ -50,12 +50,20 @@ jobs: -G "https://9c-dx.s3.ap-northeast-2.amazonaws.com/empty-genesis-block-20230511" & sleep 60s graphql-inspector introspect http://localhost:30000/graphql --write schema.graphql - - name: Build + - name: Build GraphQL Document run: | yarn global add spectaql - spectaql ./spectaql-config.yaml + spectaql --target-dir public/graphql ./spectaql-config.yaml + - name: Build CLI Document + run: | + mkdir -p public/cli + dotnet run --project NineChronicles.Headless.Executable -- \ + docs \ + public/cli + - name: Copy Landing Page to deploy + run: cp Docs/resources/landing.html public/index.html - name: Copy GraphQL Schema to deploy - run: cp schema.graphql doc + run: cp schema.graphql public/schema.graphql - name: Deploy uses: peaceiris/actions-gh-pages@v3 with: diff --git a/.github/workflows/push_docker_image.yml b/.github/workflows/push_docker_image.yml index 90f4bfd83..d603c9d2b 100644 --- a/.github/workflows/push_docker_image.yml +++ b/.github/workflows/push_docker_image.yml @@ -29,6 +29,8 @@ jobs: docker: - repo: planetariumhq/ninechronicles-headless dockerfile: Dockerfile + - repo: planetariumhq/access-control-center + dockerfile: Dockerfile.ACC if: github.ref_type == 'branch' runs-on: ubuntu-latest steps: diff --git a/Docs/resources/landing.html b/Docs/resources/landing.html new file mode 100644 index 000000000..ebe92854f --- /dev/null +++ b/Docs/resources/landing.html @@ -0,0 +1,25 @@ + + + + NineChronicles Headless + + + + + +
+
+

NineChronicles.Headless

+ A headless node to validate, network, operate NineChronicles chain. +
+ +
+
+ + diff --git a/Lib9c b/Lib9c index aa95dcffd..f8d681a6a 160000 --- a/Lib9c +++ b/Lib9c @@ -1 +1 @@ -Subproject commit aa95dcffd5f6dac64da413aa1b67bbf6a61b0be9 +Subproject commit f8d681a6a622431eacb989477071613ce2b5fc5c diff --git a/NineChronicles.Headless.AccessControlCenter/AcsService.cs b/NineChronicles.Headless.AccessControlCenter/AccService.cs similarity index 58% rename from NineChronicles.Headless.AccessControlCenter/AcsService.cs rename to NineChronicles.Headless.AccessControlCenter/AccService.cs index 596d54fad..167beac3b 100644 --- a/NineChronicles.Headless.AccessControlCenter/AcsService.cs +++ b/NineChronicles.Headless.AccessControlCenter/AccService.cs @@ -2,16 +2,16 @@ using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Server.Kestrel.Core; -using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; +using Microsoft.OpenApi.Models; using NineChronicles.Headless.AccessControlCenter.AccessControlService; namespace NineChronicles.Headless.AccessControlCenter { - public class AcsService + public class AccService { - public AcsService(Configuration configuration) + public AccService(Configuration configuration) { Configuration = configuration; } @@ -49,6 +49,33 @@ public void ConfigureServices(IServiceCollection services) { services.AddControllers(); + services.AddSwaggerGen(c => + { + c.SwaggerDoc( + "v1", + new OpenApiInfo { Title = "Access Control Center API", Version = "v1" } + ); + c.DocInclusionPredicate( + (docName, apiDesc) => + { + var controllerType = + apiDesc.ActionDescriptor + as Microsoft.AspNetCore.Mvc.Controllers.ControllerActionDescriptor; + if (controllerType != null) + { + var assemblyName = controllerType.ControllerTypeInfo.Assembly + .GetName() + .Name; + var namespaceName = controllerType.ControllerTypeInfo.Namespace; + return namespaceName?.StartsWith( + "NineChronicles.Headless.AccessControlCenter" + ) ?? false; + } + return false; + } + ); + }); + var accessControlService = MutableAccessControlServiceFactory.Create( Enum.Parse( Configuration.AccessControlServiceType, @@ -62,10 +89,11 @@ public void ConfigureServices(IServiceCollection services) public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { - if (env.IsDevelopment()) + app.UseSwagger(); + app.UseSwaggerUI(c => { - app.UseDeveloperExceptionPage(); - } + c.SwaggerEndpoint("/swagger/v1/swagger.json", "Access Control Center API V1"); + }); app.UseRouting(); app.UseAuthorization(); diff --git a/NineChronicles.Headless.AccessControlCenter/AccessControlService/IMutableAccessControlService.cs b/NineChronicles.Headless.AccessControlCenter/AccessControlService/IMutableAccessControlService.cs index aa8207174..b5d52f697 100644 --- a/NineChronicles.Headless.AccessControlCenter/AccessControlService/IMutableAccessControlService.cs +++ b/NineChronicles.Headless.AccessControlCenter/AccessControlService/IMutableAccessControlService.cs @@ -6,8 +6,8 @@ namespace NineChronicles.Headless.AccessControlCenter.AccessControlService { public interface IMutableAccessControlService : IAccessControlService { - void DenyAccess(Address address); - void AllowAccess(Address address); - List
ListBlockedAddresses(int offset, int limit); + void AddTxQuota(Address address, int quota); + void RemoveTxQuota(Address address); + List
ListTxQuotaAddresses(int offset, int limit); } } diff --git a/NineChronicles.Headless.AccessControlCenter/AccessControlService/MutableRedisAccessControlService.cs b/NineChronicles.Headless.AccessControlCenter/AccessControlService/MutableRedisAccessControlService.cs index 89c614a0a..9cb412d88 100644 --- a/NineChronicles.Headless.AccessControlCenter/AccessControlService/MutableRedisAccessControlService.cs +++ b/NineChronicles.Headless.AccessControlCenter/AccessControlService/MutableRedisAccessControlService.cs @@ -2,34 +2,42 @@ using System.Linq; using Libplanet.Crypto; using NineChronicles.Headless.Services; +using StackExchange.Redis; namespace NineChronicles.Headless.AccessControlCenter.AccessControlService { - public class MutableRedisAccessControlService : RedisAccessControlService, IMutableAccessControlService + public class MutableRedisAccessControlService + : RedisAccessControlService, + IMutableAccessControlService { - public MutableRedisAccessControlService(string storageUri) : base(storageUri) + public MutableRedisAccessControlService(string storageUri) + : base(storageUri) { } - public void DenyAccess(Address address) + public void AddTxQuota(Address address, int quota) { - _db.StringSet(address.ToString(), "denied"); + _db.StringSet(address.ToString(), quota.ToString()); } - public void AllowAccess(Address address) + public void RemoveTxQuota(Address address) { _db.KeyDelete(address.ToString()); } - public List
ListBlockedAddresses(int offset, int limit) + public List
ListTxQuotaAddresses(int offset, int limit) { var server = _db.Multiplexer.GetServer(_db.Multiplexer.GetEndPoints().First()); - return server - .Keys() - .Select(k => new Address(k.ToString())) - .Skip(offset) - .Take(limit) - .ToList(); + + var result = (RedisResult[]?) + server.Execute("SCAN", offset.ToString(), "COUNT", limit.ToString()); + if (result != null) + { + RedisKey[] keys = (RedisKey[])result[1]!; + return keys.Select(k => new Address(k.ToString())).ToList(); + } + + return new List
(); } } } diff --git a/NineChronicles.Headless.AccessControlCenter/AccessControlService/MutableSqliteAccessControlService.cs b/NineChronicles.Headless.AccessControlCenter/AccessControlService/MutableSqliteAccessControlService.cs index 1d9455118..daa2a9c37 100644 --- a/NineChronicles.Headless.AccessControlCenter/AccessControlService/MutableSqliteAccessControlService.cs +++ b/NineChronicles.Headless.AccessControlCenter/AccessControlService/MutableSqliteAccessControlService.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using Microsoft.Data.Sqlite; using Libplanet.Crypto; @@ -7,55 +8,56 @@ namespace NineChronicles.Headless.AccessControlCenter.AccessControlService { public class MutableSqliteAccessControlService : SQLiteAccessControlService, IMutableAccessControlService { - private const string DenyAccessSql = - "INSERT OR IGNORE INTO blocklist (address) VALUES (@Address)"; - private const string AllowAccessSql = "DELETE FROM blocklist WHERE address=@Address"; + private const string AddTxQuotaSql = + "INSERT OR IGNORE INTO txquotalist (address, quota) VALUES (@Address, @Quota)"; + private const string RemoveTxQuotaSql = "DELETE FROM txquotalist WHERE address=@Address"; public MutableSqliteAccessControlService(string connectionString) : base(connectionString) { } - public void DenyAccess(Address address) + public void AddTxQuota(Address address, int quota) { using var connection = new SqliteConnection(_connectionString); connection.Open(); using var command = connection.CreateCommand(); - command.CommandText = DenyAccessSql; + command.CommandText = AddTxQuotaSql; command.Parameters.AddWithValue("@Address", address.ToString()); + command.Parameters.AddWithValue("@Quota", quota); command.ExecuteNonQuery(); } - public void AllowAccess(Address address) + public void RemoveTxQuota(Address address) { using var connection = new SqliteConnection(_connectionString); connection.Open(); using var command = connection.CreateCommand(); - command.CommandText = AllowAccessSql; + command.CommandText = RemoveTxQuotaSql; command.Parameters.AddWithValue("@Address", address.ToString()); command.ExecuteNonQuery(); } - public List
ListBlockedAddresses(int offset, int limit) + public List
ListTxQuotaAddresses(int offset, int limit) { - var blockedAddresses = new List
(); + var txQuotaAddresses = new List
(); using var connection = new SqliteConnection(_connectionString); connection.Open(); using var command = connection.CreateCommand(); - command.CommandText = $"SELECT address FROM blocklist LIMIT @Limit OFFSET @Offset"; + command.CommandText = $"SELECT address FROM txquotalist LIMIT @Limit OFFSET @Offset"; command.Parameters.AddWithValue("@Limit", limit); command.Parameters.AddWithValue("@Offset", offset); using var reader = command.ExecuteReader(); while (reader.Read()) { - blockedAddresses.Add(new Address(reader.GetString(0))); + txQuotaAddresses.Add(new Address(reader.GetString(0))); } - return blockedAddresses; + return txQuotaAddresses; } } } diff --git a/NineChronicles.Headless.AccessControlCenter/Controllers/AccessControlServiceController.cs b/NineChronicles.Headless.AccessControlCenter/Controllers/AccessControlServiceController.cs index 685dca35e..40117f900 100644 --- a/NineChronicles.Headless.AccessControlCenter/Controllers/AccessControlServiceController.cs +++ b/NineChronicles.Headless.AccessControlCenter/Controllers/AccessControlServiceController.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using Microsoft.AspNetCore.Mvc; using NineChronicles.Headless.AccessControlCenter.AccessControlService; @@ -17,30 +18,50 @@ public AccessControlServiceController(IMutableAccessControlService accessControl } [HttpGet("entries/{address}")] - public ActionResult IsAccessDenied(string address) + public ActionResult GetTxQuota(string address) { - return _accessControlService.IsAccessDenied(new Address(address)); + return _accessControlService.GetTxQuota(new Address(address)); } - [HttpPost("entries/{address}/deny")] - public ActionResult DenyAccess(string address) + [HttpPost("entries/add-tx-quota/{address}/{quota:int}")] + public ActionResult AddTxQuota(string address, int quota) { - _accessControlService.DenyAccess(new Address(address)); + var maxQuota = 10; + if (quota > maxQuota) + { + return BadRequest($"The quota cannot exceed {maxQuota}."); + } + + _accessControlService.AddTxQuota(new Address(address), quota); return Ok(); } - [HttpPost("entries/{address}/allow")] - public ActionResult AllowAccess(string address) + [HttpPost("entries/remove-tx-quota/{address}")] + public ActionResult RemoveTxQuota(string address) { - _accessControlService.AllowAccess(new Address(address)); + _accessControlService.RemoveTxQuota(new Address(address)); return Ok(); } [HttpGet("entries")] public ActionResult> ListBlockedAddresses(int offset, int limit) { + var maxLimit = 10; + if (_accessControlService is MutableRedisAccessControlService) + { + maxLimit = 10; + } + else if (_accessControlService is MutableSqliteAccessControlService) + { + maxLimit = 100; + } + if (limit > maxLimit) + { + return BadRequest($"The limit cannot exceed {maxLimit}."); + } + return _accessControlService - .ListBlockedAddresses(offset, limit) + .ListTxQuotaAddresses(offset, limit) .Select(a => a.ToString()) .ToList(); } diff --git a/NineChronicles.Headless.AccessControlCenter/NineChronicles.Headless.AccessControlCenter.csproj b/NineChronicles.Headless.AccessControlCenter/NineChronicles.Headless.AccessControlCenter.csproj index 74be55c3b..1ca96b941 100644 --- a/NineChronicles.Headless.AccessControlCenter/NineChronicles.Headless.AccessControlCenter.csproj +++ b/NineChronicles.Headless.AccessControlCenter/NineChronicles.Headless.AccessControlCenter.csproj @@ -22,6 +22,7 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive + diff --git a/NineChronicles.Headless.AccessControlCenter/Program.cs b/NineChronicles.Headless.AccessControlCenter/Program.cs index ee262e673..c506b85dc 100644 --- a/NineChronicles.Headless.AccessControlCenter/Program.cs +++ b/NineChronicles.Headless.AccessControlCenter/Program.cs @@ -20,7 +20,7 @@ public static void Main(string[] args) var acsConfig = new Configuration(); config.Bind(acsConfig); - var service = new AcsService(acsConfig); + var service = new AccService(acsConfig); var hostBuilder = service.Configure(Host.CreateDefaultBuilder(), acsConfig.Port); var host = hostBuilder.Build(); host.Run(); diff --git a/NineChronicles.Headless.Executable/NineChronicles.Headless.Executable.csproj b/NineChronicles.Headless.Executable/NineChronicles.Headless.Executable.csproj index 95b3ae808..433336e26 100644 --- a/NineChronicles.Headless.Executable/NineChronicles.Headless.Executable.csproj +++ b/NineChronicles.Headless.Executable/NineChronicles.Headless.Executable.csproj @@ -20,6 +20,7 @@ + diff --git a/NineChronicles.Headless.Executable/Program.cs b/NineChronicles.Headless.Executable/Program.cs index 731fb0380..9828513a3 100644 --- a/NineChronicles.Headless.Executable/Program.cs +++ b/NineChronicles.Headless.Executable/Program.cs @@ -43,6 +43,7 @@ namespace NineChronicles.Headless.Executable { + [HasSubCommands(typeof(Cocona.Docs.DocumentCommand), "docs")] [HasSubCommands(typeof(AccountCommand), "account")] [HasSubCommands(typeof(ValidationCommand), "validation")] [HasSubCommands(typeof(ChainCommand), "chain")] diff --git a/NineChronicles.Headless.Tests/GraphTypes/States/Models/CrystalMonsterCollectionMultiplierSheetTypeTest.cs b/NineChronicles.Headless.Tests/GraphTypes/States/Models/CrystalMonsterCollectionMultiplierSheetTypeTest.cs index a1a20e7a5..491f17b83 100644 --- a/NineChronicles.Headless.Tests/GraphTypes/States/Models/CrystalMonsterCollectionMultiplierSheetTypeTest.cs +++ b/NineChronicles.Headless.Tests/GraphTypes/States/Models/CrystalMonsterCollectionMultiplierSheetTypeTest.cs @@ -59,6 +59,16 @@ public async Task Query() ["level"] = 5, ["multiplier"] = 300, }, + new Dictionary + { + ["level"] = 6, + ["multiplier"] = 300, + }, + new Dictionary + { + ["level"] = 7, + ["multiplier"] = 300, + }, }; var expected = new Dictionary { { "orderedList", list } }; Assert.Equal(expected, data); diff --git a/NineChronicles.Headless/GraphTypes/States/CombinationSlotStateType.cs b/NineChronicles.Headless/GraphTypes/States/CombinationSlotStateType.cs index b6742f078..dbc19948c 100644 --- a/NineChronicles.Headless/GraphTypes/States/CombinationSlotStateType.cs +++ b/NineChronicles.Headless/GraphTypes/States/CombinationSlotStateType.cs @@ -16,10 +16,13 @@ public CombinationSlotStateType() nameof(CombinationSlotState.UnlockBlockIndex), description: "Block index at the combination slot can be usable.", resolve: context => context.Source.UnlockBlockIndex); +#pragma warning disable CS0618 Field>( - nameof(CombinationSlotState.UnlockStage), + nameof( + CombinationSlotState.UnlockStage), description: "Stage id at the combination slot unlock.", resolve: context => context.Source.UnlockStage); +#pragma warning restore CS0618 Field>( nameof(CombinationSlotState.StartBlockIndex), description: "Block index at the combination started.", diff --git a/NineChronicles.Headless/Services/RedisAccessControlService.cs b/NineChronicles.Headless/Services/RedisAccessControlService.cs index b1dfb5e74..40769292d 100644 --- a/NineChronicles.Headless/Services/RedisAccessControlService.cs +++ b/NineChronicles.Headless/Services/RedisAccessControlService.cs @@ -1,6 +1,8 @@ +using System; using StackExchange.Redis; using Libplanet.Crypto; using Nekoyume.Blockchain; +using Serilog; namespace NineChronicles.Headless.Services { @@ -14,9 +16,17 @@ public RedisAccessControlService(string storageUri) _db = redis.GetDatabase(); } - public bool IsAccessDenied(Address address) + public int? GetTxQuota(Address address) { - return _db.KeyExists(address.ToString()); + RedisValue result = _db.StringGet(address.ToString()); + if (!result.IsNull) + { + Log.ForContext("Source", nameof(IAccessControlService)) + .Debug("\"{Address}\" Tx Quota: {Quota}", address, result); + return Convert.ToInt32(result); + } + + return null; } } } diff --git a/NineChronicles.Headless/Services/SQLiteAccessControlService.cs b/NineChronicles.Headless/Services/SQLiteAccessControlService.cs index 0a9c1e456..f3e7263c8 100644 --- a/NineChronicles.Headless/Services/SQLiteAccessControlService.cs +++ b/NineChronicles.Headless/Services/SQLiteAccessControlService.cs @@ -1,15 +1,17 @@ +using System; using Microsoft.Data.Sqlite; using Libplanet.Crypto; using Nekoyume.Blockchain; +using Serilog; namespace NineChronicles.Headless.Services { public class SQLiteAccessControlService : IAccessControlService { private const string CreateTableSql = - "CREATE TABLE IF NOT EXISTS blocklist (address VARCHAR(42))"; - private const string CheckAccessSql = - "SELECT EXISTS(SELECT 1 FROM blocklist WHERE address=@Address)"; + "CREATE TABLE IF NOT EXISTS txquotalist (address VARCHAR(42), quota INT)"; + private const string GetTxQuotaSql = + "SELECT quota FROM txquotalist WHERE address=@Address"; protected readonly string _connectionString; @@ -24,18 +26,23 @@ public SQLiteAccessControlService(string connectionString) command.ExecuteNonQuery(); } - public bool IsAccessDenied(Address address) + public int? GetTxQuota(Address address) { using var connection = new SqliteConnection(_connectionString); connection.Open(); using var command = connection.CreateCommand(); - command.CommandText = CheckAccessSql; + command.CommandText = GetTxQuotaSql; command.Parameters.AddWithValue("@Address", address.ToString()); - var result = command.ExecuteScalar(); + var queryResult = command.ExecuteScalar(); - return result is not null && (long)result == 1; + if (queryResult != null) + { + return Convert.ToInt32(queryResult); + } + + return null; } } } diff --git a/README.md b/README.md index 28430b15f..08982cd5a 100644 --- a/README.md +++ b/README.md @@ -3,166 +3,16 @@ [![Planetarium Discord invite](https://img.shields.io/discord/539405872346955788?color=6278DA&label=Planetarium&logo=discord&logoColor=white)](https://discord.gg/JyujU8E4SD) [![Planetarium-Dev Discord Invite](https://img.shields.io/discord/928926944937013338?color=6278DA&label=Planetarium-dev&logo=discord&logoColor=white)](https://discord.gg/RYJDyFRYY7) -## Table of Contents - -- [Run](#run) -- [Docker Build](#docker-build) - * [Format](#format) -- [How to run NineChronicles Headless on AWS EC2 instance using Docker](#how-to-run-ninechronicles-headless-on-aws-ec2-instance-using-docker) - * [On Your AWS EC2 Instance](#on-your-aws-ec2-instance) - * [Building Your Own Docker Image from Your Local Machine](#building-your-own-docker-image-from-your-local-machine) -- [Nine Chronicles GraphQL API Documentation](#nine-chronicles-graphql-api-documentation) -- [Create A New Genesis Block](#create-a-new-genesis-block) - ## Run +If you want to run node to interact with mainnet, you can run the below command line: + ``` -$ dotnet run --project ./NineChronicles.Headless.Executable/ -- --help - -Usage: NineChronicles.Headless.Executable [command] -Usage: NineChronicles.Headless.Executable - -// basic -[--app-protocol-version ] -[--trusted-app-protocol-version-signer ...] -[--genesis-block-path ] -[--host ] -[--port ] -[--swarm-private-key ] - -// Policy -[--skip-preload] -[--chain-tip-stale-behavior-type ] -[--confirmations ] -[--tx-life-time ] -[--message-timeout ] -[--tip-timeout ] -[--demand-buffer ] -[--tx-quota-per-signer ] -[--maximum-poll-peers ] - -// Store -[--store-type ] -[--store-path ] -[--no-reduce-store] - -// Network -[--network-type ] -[--ice-server ...] -[--peer ...] -[--static-peer ...] -[--minimum-broadcast-target ] -[--bucket-size ] - -// render -[--nonblock-renderer] -[--nonblock-renderer-queue ] -[--strict-rendering] -[--log-action-renders] - -// consensus -[--consensus-port ] -[--consensus-private-key ] -[--consensus-seed ...] - -// RPC -[--rpc-server] -[--rpc-listen-host ] -[--rpc-listen-port ] -[--rpc-remote-server] -[--rpc-http-server] - -// GraphQL -[--graphql-server] -[--graphql-host ] -[--graphql-port ] -[--graphql-secret-token-path ] -[--no-cors] - -// Sentry -[--sentry-dsn ] -[--sentry-trace-sample-rate ] - -// ETC -[--config ] -[--help] -[--version] - -// Miner (Deprecated) -[--no-miner] -[--miner-count ] -[--miner-private-key ] -[--miner.block-interval ] - - -NineChronicles.Headless.Executable - -Commands: - account - validation - chain - key - apv - action - state - tx - market - genesis - replay - -Options: - -V, --app-protocol-version App protocol version token. - -G, --genesis-block-path Genesis block path of blockchain. Blockchain is recognized by its genesis block. - -H, --host Hostname of this node for another nodes to access. This is not listening host like 0.0.0.0 - -P, --port Port of this node for another nodes to access. - --swarm-private-key The private key used for signing messages and to specify your node. If you leave this null, a randomly generated value will be used. - --no-miner Disable block mining. - --miner-count The number of miner task(thread). - --miner-private-key The private key used for mining blocks. Must not be null if you want to turn on mining with libplanet-node. - --miner.block-interval The miner's break time after mining a block. The unit is millisecond. - --store-type The type of storage to store blockchain data. If not provided, "LiteDB" will be used as default. Available type: ["rocksdb", "memory"] - --store-path Path of storage. This value is required if you use persistent storage e.g. "rocksdb" - --no-reduce-store Do not reduce storage. Enabling this option will use enormous disk spaces. - -I, --ice-server ... ICE server to NAT traverse. - --peer ... Seed peer list to communicate to another nodes. - -T, --trusted-app-protocol-version-signer ... Trustworthy signers who claim new app protocol versions - --rpc-server Use this option if you want to make unity clients to communicate with this server with RPC - --rpc-listen-host RPC listen host - --rpc-listen-port RPC listen port - --rpc-remote-server Do a role as RPC remote server? If you enable this option, multiple Unity clients can connect to your RPC server. - --rpc-http-server If you enable this option with "rpcRemoteServer" option at the same time, RPC server will use HTTP/1, not gRPC. - --graphql-server Use this option if you want to enable GraphQL server to enable querying data. - --graphql-host GraphQL listen host - --graphql-port GraphQL listen port - --graphql-secret-token-path The path to write GraphQL secret token. If you want to protect this headless application, you should use this option and take it into headers. - --no-cors Run without CORS policy. - --confirmations The number of required confirmations to recognize a block. - --nonblock-renderer Uses non-blocking renderer, which prevents the blockchain & swarm from waiting slow rendering. Turned off by default. - --nonblock-renderer-queue The size of the queue used by the non-blocking renderer. Ignored if --nonblock-renderer is turned off. - --strict-rendering Flag to turn on validating action renderer. - --log-action-renders Log action renders besides block renders. --rpc-server implies this. - --network-type Network type. (Allowed values: Main, Internal, Permanent, Test, Default) - --tx-life-time The lifetime of each transaction, which uses minute as its unit. - --message-timeout The grace period for new messages, which uses second as its unit. - --tip-timeout The grace period for tip update, which uses second as its unit. - --demand-buffer A number of block size that determines how far behind the demand the tip of the chain will publish `NodeException` to GraphQL subscriptions. - --static-peer ... A list of peers that the node will continue to maintain. - --skip-preload Run node without preloading. - --minimum-broadcast-target Minimum number of peers to broadcast message. - --bucket-size Number of the peers can be stored in each bucket. - --chain-tip-stale-behavior-type Determines behavior when the chain's tip is stale. "reboot" and "preload" is available and "reboot" option is selected by default. - --tx-quota-per-signer The number of maximum transactions can be included in stage per signer. - --maximum-poll-peers The maximum number of peers to poll blocks. int.MaxValue by default. - --consensus-port Port used for communicating consensus related messages. null by default. - --consensus-private-key The private key used for signing consensus messages. Cannot be null. - --consensus-seed ... A list of seed peers to join the block consensus. - -C, --config Absolute path of "appsettings.json" file to provide headless configurations. (Default: appsettings.json) - --sentry-dsn Sentry DSN - --sentry-trace-sample-rate Trace sample rate for sentry - -h, --help Show help message - --version Show version +dotnet run --project NineChronicles.Headless.Executable -C appsettings.mainnet.json --store-path={PATH_TO_STORE} ``` +For more information on the command line options, refer to the [CLI Documentation](https://planetarium.github.io/NineChronicles.Headless/cli). + ### Use `appsettings.{network}.json` to provide CLI options You can provide headless CLI options using file, `appsettings.json`. You'll find the default file at [here](NineChronicles.Headless.Executable/appsettings.json). @@ -286,7 +136,7 @@ $ docker push [/]:[] Check out [Nine Chronicles GraphQL API Tutorial](https://www.notion.so/Getting-Started-with-Nine-Chronicles-GraphQL-API-a14388a910844a93ab8dc0a2fe269f06) to get you started with using GraphQL API with NineChronicles Headless. -For more information on the GraphQL API, refer to the [NineChronicles Headless GraphQL Documentation](http://api.nine-chronicles.com/). +For more information on the GraphQL API, refer to the [NineChronicles Headless GraphQL Documentation](https://planetarium.github.io/NineChronicles.Headless/graphql). ---