diff --git a/NineChronicles.Headless.Executable/Configuration.cs b/NineChronicles.Headless.Executable/Configuration.cs index edf895785..29470f69d 100644 --- a/NineChronicles.Headless.Executable/Configuration.cs +++ b/NineChronicles.Headless.Executable/Configuration.cs @@ -85,6 +85,8 @@ public class Configuration public ushort? ConsensusPort { get; set; } public double? ConsensusTargetBlockIntervalMilliseconds { get; set; } + public int? MaxTransactionPerBlock { get; set; } + public string SentryDsn { get; set; } = ""; public double SentryTraceSampleRate { get; set; } = 0.01; @@ -141,6 +143,7 @@ public void Overwrite( string? consensusPrivateKeyString, string[]? consensusSeedStrings, double? consensusTargetBlockIntervalMilliseconds, + int? maxTransactionPerBlock, string? sentryDsn, double? sentryTraceSampleRate, int? arenaParticipantsSyncInterval @@ -192,6 +195,7 @@ public void Overwrite( ConsensusSeedStrings = consensusSeedStrings ?? ConsensusSeedStrings; ConsensusPrivateKeyString = consensusPrivateKeyString ?? ConsensusPrivateKeyString; ConsensusTargetBlockIntervalMilliseconds = consensusTargetBlockIntervalMilliseconds ?? ConsensusTargetBlockIntervalMilliseconds; + MaxTransactionPerBlock = maxTransactionPerBlock ?? MaxTransactionPerBlock; SentryDsn = sentryDsn ?? SentryDsn; SentryTraceSampleRate = sentryTraceSampleRate ?? SentryTraceSampleRate; ArenaParticipantsSyncInterval = arenaParticipantsSyncInterval ?? ArenaParticipantsSyncInterval; diff --git a/NineChronicles.Headless.Executable/Program.cs b/NineChronicles.Headless.Executable/Program.cs index ee45f18fb..43871c2ad 100644 --- a/NineChronicles.Headless.Executable/Program.cs +++ b/NineChronicles.Headless.Executable/Program.cs @@ -205,6 +205,9 @@ public async Task Run( [Option("consensus-target-block-interval", Description = "A target block interval used in consensus context. The unit is millisecond.")] double? consensusTargetBlockIntervalMilliseconds = null, + [Option("maximum-transaction-per-block", + Description = "Maximum transactions allowed in a block. null by default.")] + int? maxTransactionPerBlock = null, [Option("config", new[] { 'C' }, Description = "Absolute path of \"appsettings.json\" file to provide headless configurations.")] string? configPath = "appsettings.json", @@ -302,7 +305,7 @@ public async Task Run( txLifeTime, messageTimeout, tipTimeout, demandBuffer, skipPreload, minimumBroadcastTarget, bucketSize, chainTipStaleBehaviorType, txQuotaPerSigner, maximumPollPeers, consensusPort, consensusPrivateKeyString, consensusSeedStrings, consensusTargetBlockIntervalMilliseconds, - sentryDsn, sentryTraceSampleRate, arenaParticipantsSyncInterval + maxTransactionPerBlock, sentryDsn, sentryTraceSampleRate, arenaParticipantsSyncInterval ); #if SENTRY || ! DEBUG @@ -352,6 +355,14 @@ public async Task Run( ); } + if (headlessConfig.ConsensusPrivateKeyString is null && headlessConfig.MaxTransactionPerBlock is not null) + { + throw new CommandExitedException( + "--maximum-transaction-per-block can only be used when --consensus-private-key is provided.", + -1 + ); + } + if (headlessConfig.StateServiceManagerService is { } stateServiceManagerServiceOptions) { await DownloadStateServices(stateServiceManagerServiceOptions); @@ -459,6 +470,7 @@ IActionLoader MakeSingleActionLoader() MinerCount = headlessConfig.MinerCount, MinerBlockInterval = minerBlockInterval, TxQuotaPerSigner = headlessConfig.TxQuotaPerSigner, + MaxTransactionPerBlock = headlessConfig.MaxTransactionPerBlock }; var arenaMemoryCache = new StateMemoryCache(); hostBuilder.ConfigureServices(services => diff --git a/NineChronicles.Headless.Tests/GraphTypes/ActionQueryTest.cs b/NineChronicles.Headless.Tests/GraphTypes/ActionQueryTest.cs index 47ec4c484..45a5ccce9 100644 --- a/NineChronicles.Headless.Tests/GraphTypes/ActionQueryTest.cs +++ b/NineChronicles.Headless.Tests/GraphTypes/ActionQueryTest.cs @@ -1519,5 +1519,26 @@ public async Task RuneSummon() Assert.Equal(groupId, action.GroupId); Assert.Equal(summonCount, action.SummonCount); } + + [Fact] + public async Task RetrieveAvatarAssets() + { + var avatarAddress = new PrivateKey().Address; + + var query = $@"{{ + retrieveAvatarAssets( + avatarAddress: ""{avatarAddress}"" + ) + }}"; + + var queryResult = await ExecuteQueryAsync(query, standaloneContext: _standaloneContext); + var data = (Dictionary)((ExecutionNode)queryResult.Data!).ToValue()!; + var plainValue = _codec.Decode(ByteUtil.ParseHex((string)data["retrieveAvatarAssets"])); + Assert.IsType(plainValue); + var actionBase = DeserializeNCAction(plainValue); + var action = Assert.IsType(actionBase); + + Assert.Equal(avatarAddress, action.AvatarAddress); + } } } diff --git a/NineChronicles.Headless.Tests/GraphTypes/StandaloneQueryTest.cs b/NineChronicles.Headless.Tests/GraphTypes/StandaloneQueryTest.cs index 4b36b5309..4de25b8f3 100644 --- a/NineChronicles.Headless.Tests/GraphTypes/StandaloneQueryTest.cs +++ b/NineChronicles.Headless.Tests/GraphTypes/StandaloneQueryTest.cs @@ -897,7 +897,7 @@ public async Task ActivationKeyNonce(bool trim) ConsensusPeers = ImmutableList.Empty }; - var blockPolicy = NineChroniclesNodeService.GetBlockPolicy(Planet.Odin, StaticActionLoaderSingleton.Instance); + var blockPolicy = NineChroniclesNodeService.GetBlockPolicy(Planet.Odin, StaticActionLoaderSingleton.Instance, null); var service = new NineChroniclesNodeService(userPrivateKey, properties, blockPolicy, Planet.Odin, StaticActionLoaderSingleton.Instance); StandaloneContextFx.NineChroniclesNodeService = service; StandaloneContextFx.BlockChain = service.Swarm?.BlockChain; diff --git a/NineChronicles.Headless/GraphTypes/ActionQuery.cs b/NineChronicles.Headless/GraphTypes/ActionQuery.cs index 81d34b1db..2afe6d606 100644 --- a/NineChronicles.Headless/GraphTypes/ActionQuery.cs +++ b/NineChronicles.Headless/GraphTypes/ActionQuery.cs @@ -570,6 +570,7 @@ public ActionQuery(StandaloneContext standaloneContext) RegisterGarages(); RegisterSummon(); RegisterClaimItems(); + RegisterRetrieveAvatarAssets(); Field>( name: "craftQuery", diff --git a/NineChronicles.Headless/GraphTypes/ActionQueryFields/RetrieveAvatarAssets.cs b/NineChronicles.Headless/GraphTypes/ActionQueryFields/RetrieveAvatarAssets.cs new file mode 100644 index 000000000..162597f7e --- /dev/null +++ b/NineChronicles.Headless/GraphTypes/ActionQueryFields/RetrieveAvatarAssets.cs @@ -0,0 +1,33 @@ +using GraphQL; +using GraphQL.Types; +using Libplanet.Crypto; +using Libplanet.Explorer.GraphTypes; +using Nekoyume.Action; + +namespace NineChronicles.Headless.GraphTypes; + +public partial class ActionQuery +{ + private void RegisterRetrieveAvatarAssets() + { + Field>( + name: "retrieveAvatarAssets", + arguments: new QueryArguments( + new QueryArgument> + { + Name = "avatarAddress", + Description = "Avatar address to retrieve assets" + } + ), + resolve: context => + { + var avatarAddress = context.GetArgument
("avatarAddress"); + ActionBase action = new RetrieveAvatarAssets() + { + AvatarAddress = avatarAddress + }; + return Encode(context, action); + } + ); + } +} diff --git a/NineChronicles.Headless/NineChroniclesNodeService.cs b/NineChronicles.Headless/NineChroniclesNodeService.cs index 24956e28e..18f784cdf 100644 --- a/NineChronicles.Headless/NineChroniclesNodeService.cs +++ b/NineChronicles.Headless/NineChroniclesNodeService.cs @@ -203,7 +203,8 @@ StandaloneContext context IBlockPolicy blockPolicy = GetBlockPolicy( properties.Planet, - properties.ActionLoader + properties.ActionLoader, + properties.MaxTransactionPerBlock ); var service = new NineChroniclesNodeService( properties.MinerPrivateKey, @@ -256,8 +257,8 @@ StandaloneContext context return service; } - internal static IBlockPolicy GetBlockPolicy(Planet planet, IActionLoader actionLoader) - => new BlockPolicySource(actionLoader).GetPolicy(planet); + internal static IBlockPolicy GetBlockPolicy(Planet planet, IActionLoader actionLoader, int? maxTransactionPerBlock) + => new BlockPolicySource(actionLoader, maxTransactionPerBlock).GetPolicy(planet); public Task CheckPeer(string addr) => NodeService?.CheckPeer(addr) ?? throw new InvalidOperationException(); diff --git a/NineChronicles.Headless/Properties/NineChroniclesNodeServiceProperties.cs b/NineChronicles.Headless/Properties/NineChroniclesNodeServiceProperties.cs index 7d011ae97..5e365ccdc 100644 --- a/NineChronicles.Headless/Properties/NineChroniclesNodeServiceProperties.cs +++ b/NineChronicles.Headless/Properties/NineChroniclesNodeServiceProperties.cs @@ -53,6 +53,8 @@ public NineChroniclesNodeServiceProperties( public int TxQuotaPerSigner { get; set; } + public int? MaxTransactionPerBlock { get; set; } + public IActionLoader ActionLoader { get; init; } public StateServiceManagerServiceOptions? StateServiceManagerService { get; }