From 1d49ef3c5d7ad9b29000682765496cbf83d482a4 Mon Sep 17 00:00:00 2001 From: area363 Date: Tue, 21 Nov 2023 20:05:34 +0900 Subject: [PATCH 01/21] bump lib9c to main --- Lib9c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib9c b/Lib9c index dcb133894..7f504d197 160000 --- a/Lib9c +++ b/Lib9c @@ -1 +1 @@ -Subproject commit dcb13389425f05e248276924ed31b7f0686b280c +Subproject commit 7f504d197f0f159275f3901c820de4b087260445 From ffc22fc4b53679423dd83a8aea8201e5aca5d8da Mon Sep 17 00:00:00 2001 From: Suho Lee Date: Wed, 22 Nov 2023 12:30:02 +0900 Subject: [PATCH 02/21] bump: libplanet 3.6.2 --- Lib9c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib9c b/Lib9c index 7f504d197..daa5a2235 160000 --- a/Lib9c +++ b/Lib9c @@ -1 +1 @@ -Subproject commit 7f504d197f0f159275f3901c820de4b087260445 +Subproject commit daa5a2235496203fc1d1f2624b156b6980a87ccb From 3bc6ab2d6d6a3bc9b709dfa12c409b93a43fa38c Mon Sep 17 00:00:00 2001 From: Suho Lee Date: Wed, 22 Nov 2023 13:00:50 +0900 Subject: [PATCH 03/21] fetch: remove transaction size --- Lib9c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib9c b/Lib9c index daa5a2235..57a784ab5 160000 --- a/Lib9c +++ b/Lib9c @@ -1 +1 @@ -Subproject commit daa5a2235496203fc1d1f2624b156b6980a87ccb +Subproject commit 57a784ab5c5886c62b46149ed6dd44d700b3adc2 From 71d0cc5b47293ecbfe95a2ad62bda465957c56ab Mon Sep 17 00:00:00 2001 From: area363 Date: Thu, 23 Nov 2023 18:09:49 +0900 Subject: [PATCH 04/21] fix acs iterate bug --- Lib9c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib9c b/Lib9c index 57a784ab5..f897d0c35 160000 --- a/Lib9c +++ b/Lib9c @@ -1 +1 @@ -Subproject commit 57a784ab5c5886c62b46149ed6dd44d700b3adc2 +Subproject commit f897d0c351857c1faf3e29781d90b3440a2b30bb From 29c02b6a7dbec7e0c61c3980f71985a5d4bfe5a0 Mon Sep 17 00:00:00 2001 From: area363 Date: Thu, 23 Nov 2023 21:32:16 +0900 Subject: [PATCH 05/21] dynamically update quota --- Lib9c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib9c b/Lib9c index f897d0c35..9be61c241 160000 --- a/Lib9c +++ b/Lib9c @@ -1 +1 @@ -Subproject commit f897d0c351857c1faf3e29781d90b3440a2b30bb +Subproject commit 9be61c241057fbbfe5607e3f779adc2694d28874 From 1747a921af14f8849b7a5b912b9662fe4188852b Mon Sep 17 00:00:00 2001 From: area363 Date: Thu, 23 Nov 2023 23:36:00 +0900 Subject: [PATCH 06/21] bump lib9c --- Lib9c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib9c b/Lib9c index 9be61c241..095e88c60 160000 --- a/Lib9c +++ b/Lib9c @@ -1 +1 @@ -Subproject commit 9be61c241057fbbfe5607e3f779adc2694d28874 +Subproject commit 095e88c606adf4550f82d4db01173971ec5953c0 From 2e79a9ef0fbe926e55862a88266802f71e59d9d5 Mon Sep 17 00:00:00 2001 From: Yang Chun Ung Date: Fri, 24 Nov 2023 10:17:58 +0900 Subject: [PATCH 07/21] Intrdocue TransactionResults query --- .../TransactionHeadlessQueryTest.cs | 35 ++++++++ .../GraphTypes/TransactionHeadlessQuery.cs | 89 +++++++++++-------- 2 files changed, 89 insertions(+), 35 deletions(-) diff --git a/NineChronicles.Headless.Tests/GraphTypes/TransactionHeadlessQueryTest.cs b/NineChronicles.Headless.Tests/GraphTypes/TransactionHeadlessQueryTest.cs index 3a6cb5a0d..031d81040 100644 --- a/NineChronicles.Headless.Tests/GraphTypes/TransactionHeadlessQueryTest.cs +++ b/NineChronicles.Headless.Tests/GraphTypes/TransactionHeadlessQueryTest.cs @@ -342,6 +342,41 @@ public async Task TransactionResultIsSuccess() Assert.Equal("SUCCESS", txStatus); } + [Fact] + public async Task TransactionResults() + { + var privateKey = new PrivateKey(); + // Because `AddActivatedAccount` doesn't need any prerequisites. + var action = new AddActivatedAccount(default); + Transaction tx = _blockChain.MakeTransaction(privateKey, new ActionBase[] { action }); + var action2 = new DailyReward + { + avatarAddress = default + }; + Transaction tx2 = _blockChain.MakeTransaction(new PrivateKey(), new ActionBase[] { action2 }); + Block block = _blockChain.ProposeBlock(_proposer); + _blockChain.Append(block, GenerateBlockCommit(block.Index, block.Hash, _proposer)); + var queryFormat = @"query {{ + transactionResults(txIds: [""{0}"", ""{1}""]) {{ + blockHash + txStatus + }} + }}"; + var result = await ExecuteAsync(string.Format( + queryFormat, + tx.Id.ToString(), + tx2.Id.ToString() + )); + Assert.NotNull(result.Data); + var transactionResults = + (object[])((Dictionary)((ExecutionNode)result.Data!).ToValue()!)["transactionResults"]; + Assert.Equal(2, transactionResults.Length); + var txStatus = (string)((Dictionary)transactionResults[0])["txStatus"]; + Assert.Equal("SUCCESS", txStatus); + txStatus = (string)((Dictionary)transactionResults[1])["txStatus"]; + Assert.Equal("FAILURE", txStatus); + } + [Fact] public async Task NcTransactionsOnTip() { diff --git a/NineChronicles.Headless/GraphTypes/TransactionHeadlessQuery.cs b/NineChronicles.Headless/GraphTypes/TransactionHeadlessQuery.cs index 8474db53d..e7dff589b 100644 --- a/NineChronicles.Headless/GraphTypes/TransactionHeadlessQuery.cs +++ b/NineChronicles.Headless/GraphTypes/TransactionHeadlessQuery.cs @@ -192,42 +192,22 @@ public TransactionHeadlessQuery(StandaloneContext standaloneContext) ), resolve: context => { - if (!(standaloneContext.BlockChain is BlockChain blockChain)) - { - throw new ExecutionError( - $"{nameof(StandaloneContext)}.{nameof(StandaloneContext.BlockChain)} was not set yet!"); - } - - if (!(standaloneContext.Store is IStore store)) - { - throw new ExecutionError( - $"{nameof(StandaloneContext)}.{nameof(StandaloneContext.Store)} was not set yet!"); - } - - TxId txId = context.GetArgument("txId"); - if (!(store.GetFirstTxIdBlockHashIndex(txId) is { } txExecutedBlockHash)) - { - return blockChain.GetStagedTransactionIds().Contains(txId) - ? new TxResult(TxStatus.STAGING, null, null, null, null, null) - : new TxResult(TxStatus.INVALID, null, null, null, null, null); - } + var txId = context.GetArgument("txId"); + return TxResult(standaloneContext, txId); + }); - try - { - TxExecution execution = blockChain.GetTxExecution(txExecutedBlockHash, txId); - Block txExecutedBlock = blockChain[txExecutedBlockHash]; - return new TxResult( - execution.Fail ? TxStatus.FAILURE : TxStatus.SUCCESS, - txExecutedBlock.Index, - txExecutedBlock.Hash.ToString(), - execution.InputState, - execution.OutputState, - execution.ExceptionNames); - } - catch (Exception) - { - return new TxResult(TxStatus.INVALID, null, null, null, null, null); - } + Field>>( + name: "transactionResults", + arguments: new QueryArguments( + new QueryArgument>> + { Name = "txIds", Description = "transaction ids." } + ), + resolve: context => + { + return context.GetArgument>("txIds") + .AsParallel() + .AsOrdered() + .Select(txId => TxResult(standaloneContext, txId)); } ); @@ -312,6 +292,45 @@ public TransactionHeadlessQuery(StandaloneContext standaloneContext) ); } + private static object? TxResult(StandaloneContext standaloneContext, TxId txId) + { + if (!(standaloneContext.BlockChain is BlockChain blockChain)) + { + throw new ExecutionError( + $"{nameof(StandaloneContext)}.{nameof(StandaloneContext.BlockChain)} was not set yet!"); + } + + if (!(standaloneContext.Store is IStore store)) + { + throw new ExecutionError( + $"{nameof(StandaloneContext)}.{nameof(StandaloneContext.Store)} was not set yet!"); + } + + if (!(store.GetFirstTxIdBlockHashIndex(txId) is { } txExecutedBlockHash)) + { + return blockChain.GetStagedTransactionIds().Contains(txId) + ? new TxResult(TxStatus.STAGING, null, null, null, null, null) + : new TxResult(TxStatus.INVALID, null, null, null, null, null); + } + + try + { + TxExecution execution = blockChain.GetTxExecution(txExecutedBlockHash, txId); + Block txExecutedBlock = blockChain[txExecutedBlockHash]; + return new TxResult( + execution.Fail ? TxStatus.FAILURE : TxStatus.SUCCESS, + txExecutedBlock.Index, + txExecutedBlock.Hash.ToString(), + execution.InputState, + execution.OutputState, + execution.ExceptionNames); + } + catch (Exception) + { + return new TxResult(TxStatus.INVALID, null, null, null, null, null); + } + } + private IEnumerable ListBlocks(BlockChain chain, long from, long limit) { if (chain.Tip.Index < from) From 5abec57fc4d515920b226bc5c5a4ea865781daaa Mon Sep 17 00:00:00 2001 From: Yang Chun Ung Date: Mon, 4 Dec 2023 13:54:37 +0900 Subject: [PATCH 08/21] Refacot sheet caching - Use extensions - Set cache expiration for invalidate and update sheet data --- .../MemoryCacheExtensionsTest.cs | 39 +++++++++++++++++++ .../ActionEvaluationPublisher.cs | 2 +- NineChronicles.Headless/BlockChainService.cs | 9 ++--- .../MemoryCacheExtensions.cs | 35 +++++++++++++++++ 4 files changed, 78 insertions(+), 7 deletions(-) create mode 100644 NineChronicles.Headless.Tests/MemoryCacheExtensionsTest.cs create mode 100644 NineChronicles.Headless/MemoryCacheExtensions.cs diff --git a/NineChronicles.Headless.Tests/MemoryCacheExtensionsTest.cs b/NineChronicles.Headless.Tests/MemoryCacheExtensionsTest.cs new file mode 100644 index 000000000..f8550cbba --- /dev/null +++ b/NineChronicles.Headless.Tests/MemoryCacheExtensionsTest.cs @@ -0,0 +1,39 @@ +using System; +using System.Threading.Tasks; +using Bencodex; +using Bencodex.Types; +using MessagePack; +using Microsoft.Extensions.Caching.Memory; +using Microsoft.Extensions.Options; +using Nekoyume; +using Xunit; + +namespace NineChronicles.Headless.Tests; + +public class MemoryCacheExtensionsTest +{ + [Fact] + public async Task Sheet() + { + var codec = new Codec(); + var lz4Options = MessagePackSerializerOptions.Standard.WithCompression(MessagePackCompression.Lz4BlockArray); + var cache = new MemoryCache(new OptionsWrapper(new MemoryCacheOptions + { + SizeLimit = null + })); + + var sheets = TableSheetsImporter.ImportSheets(); + foreach (var pair in sheets) + { + var cacheKey = Addresses.GetSheetAddress(pair.Key).ToString(); + var value = (Text)pair.Value; + var compressed = MessagePackSerializer.Serialize(codec.Encode(value), lz4Options); + cache.SetSheet(cacheKey, value, TimeSpan.FromMilliseconds(100)); + Assert.True(cache.TryGetSheet(cacheKey, out byte[] cached)); + Assert.Equal(compressed, cached); + Assert.Equal(pair.Value, cache.GetSheet(cacheKey)); + await Task.Delay(100); + Assert.False(cache.TryGetSheet(cacheKey, out byte[] _)); + } + } +} diff --git a/NineChronicles.Headless/ActionEvaluationPublisher.cs b/NineChronicles.Headless/ActionEvaluationPublisher.cs index e3003a3c5..f6f2142a3 100644 --- a/NineChronicles.Headless/ActionEvaluationPublisher.cs +++ b/NineChronicles.Headless/ActionEvaluationPublisher.cs @@ -118,7 +118,7 @@ public ActionEvaluationPublisher( { var action = ev.Action; var sheetAddress = Addresses.GetSheetAddress(action.TableName); - _memoryCache.Set(sheetAddress.ToString(), (Text)action.TableCsv); + _memoryCache.SetSheet(sheetAddress.ToString(), (Text)action.TableCsv, TimeSpan.FromMinutes(1)); } }); } diff --git a/NineChronicles.Headless/BlockChainService.cs b/NineChronicles.Headless/BlockChainService.cs index ccd26cbfd..a05b128f7 100644 --- a/NineChronicles.Headless/BlockChainService.cs +++ b/NineChronicles.Headless/BlockChainService.cs @@ -44,7 +44,6 @@ public class BlockChainService : ServiceBase, IBlockChainSer private ActionEvaluationPublisher _publisher; private ConcurrentDictionary _sentryTraces; private MemoryCache _memoryCache; - private readonly MessagePackSerializerOptions _lz4Options; public BlockChainService( BlockChain blockChain, @@ -64,7 +63,6 @@ StateMemoryCache cache _publisher = actionEvaluationPublisher; _sentryTraces = sentryTraces; _memoryCache = cache.SheetCache; - _lz4Options = MessagePackSerializerOptions.Standard.WithCompression(MessagePackCompression.Lz4BlockArray); } public UnaryResult PutTransaction(byte[] txBytes) @@ -223,7 +221,7 @@ public UnaryResult> GetSheets( foreach (var b in addressBytesList) { var address = new Address(b); - if (_memoryCache.TryGetValue(address.ToString(), out byte[] cached)) + if (_memoryCache.TryGetSheet(address.ToString(), out byte[] cached)) { result.TryAdd(b, cached); } @@ -245,9 +243,8 @@ public UnaryResult> GetSheets( for (int i = 0; i < addresses.Count; i++) { var address = addresses[i]; - var value = _codec.Encode(values[i] ?? Null.Value); - var compressed = MessagePackSerializer.Serialize(value, _lz4Options); - _memoryCache.Set(address.ToString(), compressed); + var value = values[i] ?? Null.Value; + var compressed = _memoryCache.SetSheet(address.ToString(), value, TimeSpan.FromMinutes(1)); result.TryAdd(address.ToByteArray(), compressed); } } diff --git a/NineChronicles.Headless/MemoryCacheExtensions.cs b/NineChronicles.Headless/MemoryCacheExtensions.cs new file mode 100644 index 000000000..3cda79d22 --- /dev/null +++ b/NineChronicles.Headless/MemoryCacheExtensions.cs @@ -0,0 +1,35 @@ +using System; +using Bencodex; +using Bencodex.Types; +using MessagePack; +using Microsoft.Extensions.Caching.Memory; + +namespace NineChronicles.Headless; + +public static class MemoryCacheExtensions +{ + private static readonly Codec Codec = new Codec(); + private static readonly MessagePackSerializerOptions Lz4Options = MessagePackSerializerOptions.Standard.WithCompression(MessagePackCompression.Lz4BlockArray); + + public static byte[] SetSheet(this MemoryCache cache, string cacheKey, IValue value, TimeSpan ex) + { + var compressed = MessagePackSerializer.Serialize(Codec.Encode(value), Lz4Options); + cache.Set(cacheKey, compressed, ex); + return compressed; + } + + public static bool TryGetSheet(this MemoryCache cache, string cacheKey, out T cached) + { + return cache.TryGetValue(cacheKey, out cached); + } + + public static string? GetSheet(this MemoryCache cache, string cacheKey) + { + if (cache.TryGetSheet(cacheKey, out byte[] cached)) + { + return (Text)Codec.Decode(MessagePackSerializer.Deserialize(cached, Lz4Options)); + } + + return null; + } +} From a7ff95b2e5d1dad7c4a78930df4ab87f2fc05237 Mon Sep 17 00:00:00 2001 From: Yang Chun Ung Date: Mon, 4 Dec 2023 13:55:08 +0900 Subject: [PATCH 09/21] Introduce cached sheet query --- .../GraphTypes/StateQueryTest.cs | 34 +++++++++++++++++++ .../GraphTypes/StateQuery.cs | 20 ++++++++++- 2 files changed, 53 insertions(+), 1 deletion(-) diff --git a/NineChronicles.Headless.Tests/GraphTypes/StateQueryTest.cs b/NineChronicles.Headless.Tests/GraphTypes/StateQueryTest.cs index 038763d82..78303997c 100644 --- a/NineChronicles.Headless.Tests/GraphTypes/StateQueryTest.cs +++ b/NineChronicles.Headless.Tests/GraphTypes/StateQueryTest.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using System.Linq; using System.Security.Cryptography; @@ -15,6 +16,7 @@ using Nekoyume.Model.Garages; using Nekoyume.Model.Item; using Nekoyume.Model.State; +using Nekoyume.TableData; using NineChronicles.Headless.GraphTypes; using NineChronicles.Headless.GraphTypes.States; using NineChronicles.Headless.Tests.Common; @@ -222,6 +224,38 @@ public async Task Garage( } } + [Theory] + [InlineData(true, "expected")] + [InlineData(false, null)] + public async Task CachedSheet(bool cached, string? expected) + { + var tableName = nameof(ItemRequirementSheet); + var cache = new StateMemoryCache(); + var cacheKey = Addresses.GetSheetAddress(tableName).ToString(); + if (cached) + { + cache.SheetCache.SetSheet(cacheKey, (Text)expected, TimeSpan.FromMinutes(1)); + } + var query = $"{{ cachedSheet(tableName: \"{tableName}\") }}"; + MockState mockState = MockState.Empty; + var queryResult = await ExecuteQueryAsync( + query, + source: new StateContext( + mockState, + 0L, cache)); + Assert.Null(queryResult.Errors); + var data = (Dictionary)((ExecutionNode)queryResult.Data!).ToValue()!; + Assert.Equal(cached, cache.SheetCache.TryGetSheet(cacheKey, out byte[] _)); + if (cached) + { + Assert.Equal(expected, data["cachedSheet"]); + } + else + { + Assert.Null(data["cachedSheet"]); + } + } + private static IEnumerable GetMemberDataOfGarages() { var agentAddr = new PrivateKey().ToAddress(); diff --git a/NineChronicles.Headless/GraphTypes/StateQuery.cs b/NineChronicles.Headless/GraphTypes/StateQuery.cs index c2f6d197c..4f0e96193 100644 --- a/NineChronicles.Headless/GraphTypes/StateQuery.cs +++ b/NineChronicles.Headless/GraphTypes/StateQuery.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using Bencodex; using Bencodex.Types; using GraphQL; using GraphQL.Types; @@ -11,7 +12,6 @@ using Nekoyume; using Nekoyume.Action; using Nekoyume.Arena; -using Nekoyume.Battle; using Nekoyume.Extensions; using Nekoyume.Model.Arena; using Nekoyume.Model.EnumType; @@ -32,6 +32,8 @@ namespace NineChronicles.Headless.GraphTypes { public partial class StateQuery : ObjectGraphType { + private readonly Codec _codec = new Codec(); + public StateQuery() { Name = "StateQuery"; @@ -699,6 +701,22 @@ public StateQuery() return result; } ); + + Field( + name: "cachedSheet", + arguments: new QueryArguments( + new QueryArgument + { + Name = "tableName" + } + ), + resolve: context => + { + var tableName = context.GetArgument("tableName"); + var cacheKey = Addresses.GetSheetAddress(tableName).ToString(); + return context.Source.StateMemoryCache.SheetCache.GetSheet(cacheKey); + } + ); } public static List GetRuneOptions( From 5a38afce5f9c12f82302cf4d8274bf4f4d70beb5 Mon Sep 17 00:00:00 2001 From: area363 Date: Mon, 4 Dec 2023 17:31:05 +0900 Subject: [PATCH 10/21] bump lib9c to main --- Lib9c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib9c b/Lib9c index 095e88c60..275f31998 160000 --- a/Lib9c +++ b/Lib9c @@ -1 +1 @@ -Subproject commit 095e88c606adf4550f82d4db01173971ec5953c0 +Subproject commit 275f3199855b319bbca490789d4bad3607fcceb5 From c30000acb9588481f9d537400d9d051415a1ae9b Mon Sep 17 00:00:00 2001 From: area363 Date: Mon, 4 Dec 2023 17:35:19 +0900 Subject: [PATCH 11/21] fix lint --- NineChronicles.Headless/GraphTypes/TransactionHeadlessQuery.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NineChronicles.Headless/GraphTypes/TransactionHeadlessQuery.cs b/NineChronicles.Headless/GraphTypes/TransactionHeadlessQuery.cs index e7dff589b..0632d7ca9 100644 --- a/NineChronicles.Headless/GraphTypes/TransactionHeadlessQuery.cs +++ b/NineChronicles.Headless/GraphTypes/TransactionHeadlessQuery.cs @@ -200,7 +200,7 @@ public TransactionHeadlessQuery(StandaloneContext standaloneContext) name: "transactionResults", arguments: new QueryArguments( new QueryArgument>> - { Name = "txIds", Description = "transaction ids." } + { Name = "txIds", Description = "transaction ids." } ), resolve: context => { From 5f2839c8d3542a78c84e1a94f91f27d1983ea76c Mon Sep 17 00:00:00 2001 From: area363 Date: Mon, 4 Dec 2023 17:53:28 +0900 Subject: [PATCH 12/21] fix test --- .../MemoryCacheExtensionsTest.cs | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/NineChronicles.Headless.Tests/MemoryCacheExtensionsTest.cs b/NineChronicles.Headless.Tests/MemoryCacheExtensionsTest.cs index f8550cbba..d5d016d7e 100644 --- a/NineChronicles.Headless.Tests/MemoryCacheExtensionsTest.cs +++ b/NineChronicles.Headless.Tests/MemoryCacheExtensionsTest.cs @@ -6,6 +6,7 @@ using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.Options; using Nekoyume; +using Nekoyume.TableData; using Xunit; namespace NineChronicles.Headless.Tests; @@ -23,17 +24,16 @@ public async Task Sheet() })); var sheets = TableSheetsImporter.ImportSheets(); - foreach (var pair in sheets) - { - var cacheKey = Addresses.GetSheetAddress(pair.Key).ToString(); - var value = (Text)pair.Value; - var compressed = MessagePackSerializer.Serialize(codec.Encode(value), lz4Options); - cache.SetSheet(cacheKey, value, TimeSpan.FromMilliseconds(100)); - Assert.True(cache.TryGetSheet(cacheKey, out byte[] cached)); - Assert.Equal(compressed, cached); - Assert.Equal(pair.Value, cache.GetSheet(cacheKey)); - await Task.Delay(100); - Assert.False(cache.TryGetSheet(cacheKey, out byte[] _)); - } + var tableName = nameof(ItemRequirementSheet); + var csv = sheets[tableName]; + var cacheKey = Addresses.GetSheetAddress(tableName).ToString(); + var value = (Text)csv; + var compressed = MessagePackSerializer.Serialize(codec.Encode(value), lz4Options); + cache.SetSheet(cacheKey, value, TimeSpan.FromMilliseconds(100)); + Assert.True(cache.TryGetValue(cacheKey, out byte[] cached)); + Assert.Equal(compressed, cached); + Assert.Equal(csv, cache.GetSheet(cacheKey)); + await Task.Delay(100); + Assert.False(cache.TryGetValue(cacheKey, out byte[] _)); } } From 724a506aea9045f9018310fabceb73136303d66f Mon Sep 17 00:00:00 2001 From: ilgyu Date: Thu, 30 Nov 2023 11:59:02 +0900 Subject: [PATCH 13/21] feat: Introduce plugin AEV --- ...t.Extensions.PluggedActionEvaluator.csproj | 15 +++++ .../PluggedActionEvaluator.cs | 50 ++++++++++++++++ .../PluginLoadContext.cs | 36 +++++++++++ .../Hosting/ActionEvaluatorType.cs | 1 + .../Hosting/LibplanetNodeService.cs | 31 ++++++---- .../PluggedActionEvaluatorConfiguration.cs | 10 ++++ Libplanet.Headless/Libplanet.Headless.csproj | 1 + NineChronicles.Headless.Executable.sln | 60 +++++++++++++++---- .../appsettings-schema.json | 20 ++++++- 9 files changed, 199 insertions(+), 25 deletions(-) create mode 100644 Libplanet.Extensions.PluggedActionEvaluator/Libplanet.Extensions.PluggedActionEvaluator.csproj create mode 100644 Libplanet.Extensions.PluggedActionEvaluator/PluggedActionEvaluator.cs create mode 100644 Libplanet.Extensions.PluggedActionEvaluator/PluginLoadContext.cs create mode 100644 Libplanet.Headless/Hosting/PluggedActionEvaluatorConfiguration.cs diff --git a/Libplanet.Extensions.PluggedActionEvaluator/Libplanet.Extensions.PluggedActionEvaluator.csproj b/Libplanet.Extensions.PluggedActionEvaluator/Libplanet.Extensions.PluggedActionEvaluator.csproj new file mode 100644 index 000000000..78e3b7dcd --- /dev/null +++ b/Libplanet.Extensions.PluggedActionEvaluator/Libplanet.Extensions.PluggedActionEvaluator.csproj @@ -0,0 +1,15 @@ + + + + net6.0 + enable + enable + + + + + + + + + diff --git a/Libplanet.Extensions.PluggedActionEvaluator/PluggedActionEvaluator.cs b/Libplanet.Extensions.PluggedActionEvaluator/PluggedActionEvaluator.cs new file mode 100644 index 000000000..ca41c2baf --- /dev/null +++ b/Libplanet.Extensions.PluggedActionEvaluator/PluggedActionEvaluator.cs @@ -0,0 +1,50 @@ +using System.Reflection; +using System.Security.Cryptography; +using Lib9c.Plugin.Shared; +using Libplanet.Action; +using Libplanet.Action.Loader; +using Libplanet.Common; +using Libplanet.Extensions.ActionEvaluatorCommonComponents; +using Libplanet.Types.Blocks; + +namespace Libplanet.Extensions.PluggedActionEvaluator +{ + public class PluggedActionEvaluator : IActionEvaluator + { + private readonly IPluginActionEvaluator _pluginActionEvaluator; + + public IActionLoader ActionLoader => throw new NotImplementedException(); + + public PluggedActionEvaluator(string pluginPath, string typeName, string stateStorePath) + { + _pluginActionEvaluator = CreateActionEvaluator(pluginPath, typeName, stateStorePath); + } + + public static Assembly LoadPlugin(string absolutePath) + { + PluginLoadContext loadContext = new PluginLoadContext(absolutePath); + return loadContext.LoadFromAssemblyName(new AssemblyName(Path.GetFileNameWithoutExtension(absolutePath))); + } + + public static IPluginActionEvaluator CreateActionEvaluator(Assembly assembly, string typeName, string stateStorePath) + { + if (assembly.GetType(typeName) is Type type && + Activator.CreateInstance(type, args: stateStorePath) as IPluginActionEvaluator + is IPluginActionEvaluator pluginActionEvaluator) + { + return pluginActionEvaluator; + } + + throw new NullReferenceException("PluginActionEvaluator not found with given parameters"); + } + + public static IPluginActionEvaluator CreateActionEvaluator(string pluginPath, string typeName, string stateStorePath) + => CreateActionEvaluator(LoadPlugin(pluginPath), typeName, stateStorePath); + + public IReadOnlyList Evaluate(IPreEvaluationBlock block, HashDigest? baseStateRootHash) + => _pluginActionEvaluator.Evaluate( + PreEvaluationBlockMarshaller.Serialize(block), + baseStateRootHash is { } srh ? srh.ToByteArray() : null) + .Select(eval => ActionEvaluationMarshaller.Deserialize(eval)).ToList().AsReadOnly(); + } +} diff --git a/Libplanet.Extensions.PluggedActionEvaluator/PluginLoadContext.cs b/Libplanet.Extensions.PluggedActionEvaluator/PluginLoadContext.cs new file mode 100644 index 000000000..497e9a791 --- /dev/null +++ b/Libplanet.Extensions.PluggedActionEvaluator/PluginLoadContext.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.Loader; + +namespace Libplanet.Extensions.PluggedActionEvaluator +{ + public class PluginLoadContext : AssemblyLoadContext + { + private readonly AssemblyDependencyResolver _resolver; + + public PluginLoadContext(string pluginPath) + { + _resolver = new AssemblyDependencyResolver(pluginPath); + } + + protected override Assembly? Load(AssemblyName assemblyName) + { + if (_resolver.ResolveAssemblyToPath(assemblyName) is { } assemblyPath) + { + return LoadFromAssemblyPath(assemblyPath); + } + + return null; + } + + protected override IntPtr LoadUnmanagedDll(string unmanagedDllName) + { + if (_resolver.ResolveUnmanagedDllToPath(unmanagedDllName) is { } libraryPath) + { + return LoadUnmanagedDllFromPath(libraryPath); + } + + return IntPtr.Zero; + } + } +} + diff --git a/Libplanet.Headless/Hosting/ActionEvaluatorType.cs b/Libplanet.Headless/Hosting/ActionEvaluatorType.cs index 9d9498b61..616d0d2d1 100644 --- a/Libplanet.Headless/Hosting/ActionEvaluatorType.cs +++ b/Libplanet.Headless/Hosting/ActionEvaluatorType.cs @@ -5,4 +5,5 @@ public enum ActionEvaluatorType Default, // ActionEvaluator ForkableActionEvaluator, RemoteActionEvaluator, + PluggedActionEvaluator, } diff --git a/Libplanet.Headless/Hosting/LibplanetNodeService.cs b/Libplanet.Headless/Hosting/LibplanetNodeService.cs index cc5a3d55a..11ce03a43 100644 --- a/Libplanet.Headless/Hosting/LibplanetNodeService.cs +++ b/Libplanet.Headless/Hosting/LibplanetNodeService.cs @@ -15,6 +15,7 @@ using Libplanet.Types.Blocks; using Libplanet.Crypto; using Libplanet.Extensions.ForkableActionEvaluator; +using Libplanet.Extensions.PluggedActionEvaluator; using Libplanet.Extensions.RemoteActionEvaluator; using Libplanet.Net; using Libplanet.Net.Consensus; @@ -120,19 +121,23 @@ IActionEvaluator BuildActionEvaluator(IActionEvaluatorConfiguration actionEvalua { return actionEvaluatorConfiguration switch { - RemoteActionEvaluatorConfiguration remoteActionEvaluatorConfiguration => new RemoteActionEvaluator( - new Uri(remoteActionEvaluatorConfiguration.StateServiceEndpoint)), - DefaultActionEvaluatorConfiguration _ => new ActionEvaluator( - _ => blockPolicy.BlockAction, - stateStore: StateStore, - actionTypeLoader: actionLoader - ), - ForkableActionEvaluatorConfiguration forkableActionEvaluatorConfiguration => new - ForkableActionEvaluator( - forkableActionEvaluatorConfiguration.Pairs.Select(pair => ( - (pair.Item1.Start, pair.Item1.End), BuildActionEvaluator(pair.Item2) - )) - ), + PluggedActionEvaluatorConfiguration pluginActionEvaluatorConfiguration => + new PluggedActionEvaluator( + pluginActionEvaluatorConfiguration.PluginPath, + pluginActionEvaluatorConfiguration.TypeName, + Path.Combine(Properties.StorePath, "states")), + RemoteActionEvaluatorConfiguration remoteActionEvaluatorConfiguration => + new RemoteActionEvaluator( + new Uri(remoteActionEvaluatorConfiguration.StateServiceEndpoint)), + DefaultActionEvaluatorConfiguration _ => + new ActionEvaluator( + _ => blockPolicy.BlockAction, + stateStore: StateStore, + actionTypeLoader: actionLoader), + ForkableActionEvaluatorConfiguration forkableActionEvaluatorConfiguration => + new ForkableActionEvaluator( + forkableActionEvaluatorConfiguration.Pairs.Select( + pair => ((pair.Item1.Start, pair.Item1.End), BuildActionEvaluator(pair.Item2)))), _ => throw new InvalidOperationException("Unexpected type."), }; } diff --git a/Libplanet.Headless/Hosting/PluggedActionEvaluatorConfiguration.cs b/Libplanet.Headless/Hosting/PluggedActionEvaluatorConfiguration.cs new file mode 100644 index 000000000..a58c540ac --- /dev/null +++ b/Libplanet.Headless/Hosting/PluggedActionEvaluatorConfiguration.cs @@ -0,0 +1,10 @@ +namespace Libplanet.Headless.Hosting; + +public class PluggedActionEvaluatorConfiguration : IActionEvaluatorConfiguration +{ + public ActionEvaluatorType Type => ActionEvaluatorType.PluggedActionEvaluator; + + public string PluginPath { get; init; } + + public string TypeName { get; init; } +} diff --git a/Libplanet.Headless/Libplanet.Headless.csproj b/Libplanet.Headless/Libplanet.Headless.csproj index 94ce16144..3ada062b3 100644 --- a/Libplanet.Headless/Libplanet.Headless.csproj +++ b/Libplanet.Headless/Libplanet.Headless.csproj @@ -25,5 +25,6 @@ + diff --git a/NineChronicles.Headless.Executable.sln b/NineChronicles.Headless.Executable.sln index b14931789..4447ecc4e 100644 --- a/NineChronicles.Headless.Executable.sln +++ b/NineChronicles.Headless.Executable.sln @@ -60,25 +60,29 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Libplanet.Extensions.Forkab EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".Libplanet", ".Libplanet", "{69F04D28-2B2E-454D-9B15-4D708EEEA8B5}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Libplanet.Action", "Lib9c\.Libplanet\Libplanet.Action\Libplanet.Action.csproj", "{EB464A50-9976-4DEA-B170-F72C4FB73A9C}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Libplanet.Action", "Lib9c\.Libplanet\Libplanet.Action\Libplanet.Action.csproj", "{EB464A50-9976-4DEA-B170-F72C4FB73A9C}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Libplanet.Common", "Lib9c\.Libplanet\Libplanet.Common\Libplanet.Common.csproj", "{95FB2620-540C-4498-9DAE-65198E89680C}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Libplanet.Common", "Lib9c\.Libplanet\Libplanet.Common\Libplanet.Common.csproj", "{95FB2620-540C-4498-9DAE-65198E89680C}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Libplanet.Types", "Lib9c\.Libplanet\Libplanet.Types\Libplanet.Types.csproj", "{FC65B031-F6EE-4561-A365-47B6FDD1C114}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Libplanet.Types", "Lib9c\.Libplanet\Libplanet.Types\Libplanet.Types.csproj", "{FC65B031-F6EE-4561-A365-47B6FDD1C114}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Libplanet.Store", "Lib9c\.Libplanet\Libplanet.Store\Libplanet.Store.csproj", "{2FF6DADC-5E7A-4F03-94D5-2CF50DED8C29}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Libplanet.Store", "Lib9c\.Libplanet\Libplanet.Store\Libplanet.Store.csproj", "{2FF6DADC-5E7A-4F03-94D5-2CF50DED8C29}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Libplanet.Crypto", "Lib9c\.Libplanet\Libplanet.Crypto\Libplanet.Crypto.csproj", "{2C3AD392-38A1-4E07-B1F9-694EE4A1E0C0}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Libplanet.Crypto", "Lib9c\.Libplanet\Libplanet.Crypto\Libplanet.Crypto.csproj", "{2C3AD392-38A1-4E07-B1F9-694EE4A1E0C0}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Libplanet.Extensions.ActionEvaluatorCommonComponents", "Lib9c\.Libplanet.Extensions.ActionEvaluatorCommonComponents\Libplanet.Extensions.ActionEvaluatorCommonComponents.csproj", "{A6922395-36E5-4B0A-BEBD-9BCE34D08722}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Libplanet.Extensions.ActionEvaluatorCommonComponents", "Lib9c\.Libplanet.Extensions.ActionEvaluatorCommonComponents\Libplanet.Extensions.ActionEvaluatorCommonComponents.csproj", "{A6922395-36E5-4B0A-BEBD-9BCE34D08722}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lib9c.StateService.Shared", "Lib9c\.Lib9c.StateService.Shared\Lib9c.StateService.Shared.csproj", "{6A410F06-134A-46D9-8B39-381FA2ED861F}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Lib9c.StateService.Shared", "Lib9c\.Lib9c.StateService.Shared\Lib9c.StateService.Shared.csproj", "{6A410F06-134A-46D9-8B39-381FA2ED861F}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Libplanet.Extensions.RemoteActionEvaluator", "Lib9c\.Libplanet.Extensions.RemoteActionEvaluator\Libplanet.Extensions.RemoteActionEvaluator.csproj", "{C41BA817-5D5B-42A5-9CF8-E8F29D1B71EF}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Libplanet.Extensions.RemoteActionEvaluator", "Lib9c\.Libplanet.Extensions.RemoteActionEvaluator\Libplanet.Extensions.RemoteActionEvaluator.csproj", "{C41BA817-5D5B-42A5-9CF8-E8F29D1B71EF}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Libplanet.Extensions.RemoteBlockChainStates", "Lib9c\.Libplanet.Extensions.RemoteBlockChainStates\Libplanet.Extensions.RemoteBlockChainStates.csproj", "{8F9E5505-C157-4DF3-A419-FF0108731397}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Libplanet.Extensions.RemoteBlockChainStates", "Lib9c\.Libplanet.Extensions.RemoteBlockChainStates\Libplanet.Extensions.RemoteBlockChainStates.csproj", "{8F9E5505-C157-4DF3-A419-FF0108731397}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NineChronicles.Headless.AccessControlCenter", "NineChronicles.Headless.AccessControlCenter\NineChronicles.Headless.AccessControlCenter.csproj", "{162C0F4B-A1D9-4132-BC34-31F1247BC26B}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NineChronicles.Headless.AccessControlCenter", "NineChronicles.Headless.AccessControlCenter\NineChronicles.Headless.AccessControlCenter.csproj", "{162C0F4B-A1D9-4132-BC34-31F1247BC26B}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Libplanet.Extensions.PluggedActionEvaluator", "Libplanet.Extensions.PluggedActionEvaluator\Libplanet.Extensions.PluggedActionEvaluator.csproj", "{DE91C36D-3999-47B6-A0BD-848C8EBA2A76}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Lib9c.Plugin.Shared", "Lib9c\.Lib9c.Plugin.Shared\Lib9c.Plugin.Shared.csproj", "{3D32DA34-E619-429F-8421-848FF4F14417}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -723,6 +727,42 @@ Global {162C0F4B-A1D9-4132-BC34-31F1247BC26B}.Release|x64.Build.0 = Release|Any CPU {162C0F4B-A1D9-4132-BC34-31F1247BC26B}.Release|x86.ActiveCfg = Release|Any CPU {162C0F4B-A1D9-4132-BC34-31F1247BC26B}.Release|x86.Build.0 = Release|Any CPU + {DE91C36D-3999-47B6-A0BD-848C8EBA2A76}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DE91C36D-3999-47B6-A0BD-848C8EBA2A76}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DE91C36D-3999-47B6-A0BD-848C8EBA2A76}.Debug|x64.ActiveCfg = Debug|Any CPU + {DE91C36D-3999-47B6-A0BD-848C8EBA2A76}.Debug|x64.Build.0 = Debug|Any CPU + {DE91C36D-3999-47B6-A0BD-848C8EBA2A76}.Debug|x86.ActiveCfg = Debug|Any CPU + {DE91C36D-3999-47B6-A0BD-848C8EBA2A76}.Debug|x86.Build.0 = Debug|Any CPU + {DE91C36D-3999-47B6-A0BD-848C8EBA2A76}.DevEx|Any CPU.ActiveCfg = Debug|Any CPU + {DE91C36D-3999-47B6-A0BD-848C8EBA2A76}.DevEx|Any CPU.Build.0 = Debug|Any CPU + {DE91C36D-3999-47B6-A0BD-848C8EBA2A76}.DevEx|x64.ActiveCfg = Debug|Any CPU + {DE91C36D-3999-47B6-A0BD-848C8EBA2A76}.DevEx|x64.Build.0 = Debug|Any CPU + {DE91C36D-3999-47B6-A0BD-848C8EBA2A76}.DevEx|x86.ActiveCfg = Debug|Any CPU + {DE91C36D-3999-47B6-A0BD-848C8EBA2A76}.DevEx|x86.Build.0 = Debug|Any CPU + {DE91C36D-3999-47B6-A0BD-848C8EBA2A76}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DE91C36D-3999-47B6-A0BD-848C8EBA2A76}.Release|Any CPU.Build.0 = Release|Any CPU + {DE91C36D-3999-47B6-A0BD-848C8EBA2A76}.Release|x64.ActiveCfg = Release|Any CPU + {DE91C36D-3999-47B6-A0BD-848C8EBA2A76}.Release|x64.Build.0 = Release|Any CPU + {DE91C36D-3999-47B6-A0BD-848C8EBA2A76}.Release|x86.ActiveCfg = Release|Any CPU + {DE91C36D-3999-47B6-A0BD-848C8EBA2A76}.Release|x86.Build.0 = Release|Any CPU + {3D32DA34-E619-429F-8421-848FF4F14417}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3D32DA34-E619-429F-8421-848FF4F14417}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3D32DA34-E619-429F-8421-848FF4F14417}.Debug|x64.ActiveCfg = Debug|Any CPU + {3D32DA34-E619-429F-8421-848FF4F14417}.Debug|x64.Build.0 = Debug|Any CPU + {3D32DA34-E619-429F-8421-848FF4F14417}.Debug|x86.ActiveCfg = Debug|Any CPU + {3D32DA34-E619-429F-8421-848FF4F14417}.Debug|x86.Build.0 = Debug|Any CPU + {3D32DA34-E619-429F-8421-848FF4F14417}.DevEx|Any CPU.ActiveCfg = Debug|Any CPU + {3D32DA34-E619-429F-8421-848FF4F14417}.DevEx|Any CPU.Build.0 = Debug|Any CPU + {3D32DA34-E619-429F-8421-848FF4F14417}.DevEx|x64.ActiveCfg = Debug|Any CPU + {3D32DA34-E619-429F-8421-848FF4F14417}.DevEx|x64.Build.0 = Debug|Any CPU + {3D32DA34-E619-429F-8421-848FF4F14417}.DevEx|x86.ActiveCfg = Debug|Any CPU + {3D32DA34-E619-429F-8421-848FF4F14417}.DevEx|x86.Build.0 = Debug|Any CPU + {3D32DA34-E619-429F-8421-848FF4F14417}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3D32DA34-E619-429F-8421-848FF4F14417}.Release|Any CPU.Build.0 = Release|Any CPU + {3D32DA34-E619-429F-8421-848FF4F14417}.Release|x64.ActiveCfg = Release|Any CPU + {3D32DA34-E619-429F-8421-848FF4F14417}.Release|x64.Build.0 = Release|Any CPU + {3D32DA34-E619-429F-8421-848FF4F14417}.Release|x86.ActiveCfg = Release|Any CPU + {3D32DA34-E619-429F-8421-848FF4F14417}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/NineChronicles.Headless.Executable/appsettings-schema.json b/NineChronicles.Headless.Executable/appsettings-schema.json index 19dd91e11..83a714594 100644 --- a/NineChronicles.Headless.Executable/appsettings-schema.json +++ b/NineChronicles.Headless.Executable/appsettings-schema.json @@ -217,7 +217,23 @@ "type": "string" } }, - "required": ["stateServiceEndpoint"], + "required": [ "stateServiceEndpoint" ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "type": { + "const": "PluggedActionEvaluator" + }, + "pluginTypeName": { + "type": "string" + }, + "pluginPath": { + "type": "string" + } + }, + "required": [ "pluginPath", "TypeName" ], "additionalProperties": false }, { @@ -238,7 +254,7 @@ "$ref": "#/definitions/action-evaluator" } }, - "required": ["range", "actionEvaluator"], + "required": [ "range", "actionEvaluator" ], "additionalProperties": false } } From 184eb425f7fa6d328a45f2e4a267ee1dc7339cad Mon Sep 17 00:00:00 2001 From: Suho Lee Date: Thu, 30 Nov 2023 16:40:26 +0900 Subject: [PATCH 14/21] feat: implement `IPluginKeyValueStore` and adapter for `IKeyValueStore` --- Lib9c | 2 +- .../PluginKeyValueStore.cs | 42 ++++++++++++++++++ .../WrappedKeyValueStore.cs | 44 +++++++++++++++++++ 3 files changed, 87 insertions(+), 1 deletion(-) create mode 100644 Libplanet.Extensions.PluggedActionEvaluator/PluginKeyValueStore.cs create mode 100644 Libplanet.Extensions.PluggedActionEvaluator/WrappedKeyValueStore.cs diff --git a/Lib9c b/Lib9c index 275f31998..7f504d197 160000 --- a/Lib9c +++ b/Lib9c @@ -1 +1 @@ -Subproject commit 275f3199855b319bbca490789d4bad3607fcceb5 +Subproject commit 7f504d197f0f159275f3901c820de4b087260445 diff --git a/Libplanet.Extensions.PluggedActionEvaluator/PluginKeyValueStore.cs b/Libplanet.Extensions.PluggedActionEvaluator/PluginKeyValueStore.cs new file mode 100644 index 000000000..e2172fff5 --- /dev/null +++ b/Libplanet.Extensions.PluggedActionEvaluator/PluginKeyValueStore.cs @@ -0,0 +1,42 @@ +using System.Collections.Immutable; +using Lib9c.Plugin.Shared; +using Libplanet.Store.Trie; + +namespace Libplanet.Extensions.PluggedActionEvaluator +{ + public class PluginKeyValueStore : IPluginKeyValueStore + { + private readonly IKeyValueStore _keyValueStore; + + public PluginKeyValueStore(IKeyValueStore keyValueStore) + { + _keyValueStore = keyValueStore; + } + public byte[] Get(in ImmutableArray key) => + _keyValueStore.Get(new KeyBytes(key)); + + public void Set(in ImmutableArray key, byte[] value) => + _keyValueStore.Set(new KeyBytes(key), value); + + public void Set(IDictionary, byte[]> values) => + _keyValueStore.Set( + values.ToDictionary(kv => + new KeyBytes(kv.Key), kv => kv.Value)); + + public void Delete(in ImmutableArray key) => + _keyValueStore.Delete(new KeyBytes(key)); + + public void Delete(IEnumerable> keys) => + _keyValueStore.Delete( + keys.Select(key => new KeyBytes(key))); + + public bool Exists(in ImmutableArray key) => + _keyValueStore.Exists(new KeyBytes(key)); + + public IEnumerable> ListKeys() => + _keyValueStore.ListKeys().Select(key => key.ByteArray); + + public void Dispose() => + _keyValueStore.Dispose(); + } +} diff --git a/Libplanet.Extensions.PluggedActionEvaluator/WrappedKeyValueStore.cs b/Libplanet.Extensions.PluggedActionEvaluator/WrappedKeyValueStore.cs new file mode 100644 index 000000000..19e7042f1 --- /dev/null +++ b/Libplanet.Extensions.PluggedActionEvaluator/WrappedKeyValueStore.cs @@ -0,0 +1,44 @@ +using Lib9c.Plugin.Shared; +using Libplanet.Store.Trie; + +namespace Libplanet.Extensions.PluggedActionEvaluator +{ + public class WrappedKeyValueStore: IKeyValueStore + { + private readonly IPluginKeyValueStore _pluginKeyValueStore; + + public WrappedKeyValueStore(IPluginKeyValueStore pluginKeyValueStore) + { + _pluginKeyValueStore = pluginKeyValueStore; + } + + public void Dispose() + { + _pluginKeyValueStore.Dispose(); + } + + public byte[] Get(in KeyBytes key) => + _pluginKeyValueStore.Get(key.ByteArray); + + public void Set(in KeyBytes key, byte[] value) => + _pluginKeyValueStore.Set(key.ByteArray, value); + + public void Set(IDictionary values) => + _pluginKeyValueStore.Set( + values.ToDictionary(kv => + kv.Key.ByteArray, kv => kv.Value)); + + public void Delete(in KeyBytes key) => + _pluginKeyValueStore.Delete(key.ByteArray); + + public void Delete(IEnumerable keys) => + _pluginKeyValueStore.Delete( + keys.Select(key => key.ByteArray)); + + public bool Exists(in KeyBytes key) => + _pluginKeyValueStore.Exists(key.ByteArray); + + public IEnumerable ListKeys() => + _pluginKeyValueStore.ListKeys().Select(key => new KeyBytes(key)); + } +} From a4e1b3c9238ee0688750894a7808c80b4c36df95 Mon Sep 17 00:00:00 2001 From: Suho Lee Date: Thu, 30 Nov 2023 16:41:12 +0900 Subject: [PATCH 15/21] feat: use `IKeyValueStore` instead of path --- .../PluggedActionEvaluator.cs | 13 +++++++------ Libplanet.Headless/Hosting/LibplanetNodeService.cs | 8 ++++---- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/Libplanet.Extensions.PluggedActionEvaluator/PluggedActionEvaluator.cs b/Libplanet.Extensions.PluggedActionEvaluator/PluggedActionEvaluator.cs index ca41c2baf..adf9fce96 100644 --- a/Libplanet.Extensions.PluggedActionEvaluator/PluggedActionEvaluator.cs +++ b/Libplanet.Extensions.PluggedActionEvaluator/PluggedActionEvaluator.cs @@ -5,6 +5,7 @@ using Libplanet.Action.Loader; using Libplanet.Common; using Libplanet.Extensions.ActionEvaluatorCommonComponents; +using Libplanet.Store.Trie; using Libplanet.Types.Blocks; namespace Libplanet.Extensions.PluggedActionEvaluator @@ -15,9 +16,9 @@ public class PluggedActionEvaluator : IActionEvaluator public IActionLoader ActionLoader => throw new NotImplementedException(); - public PluggedActionEvaluator(string pluginPath, string typeName, string stateStorePath) + public PluggedActionEvaluator(string pluginPath, string typeName, IKeyValueStore keyValueStore) { - _pluginActionEvaluator = CreateActionEvaluator(pluginPath, typeName, stateStorePath); + _pluginActionEvaluator = CreateActionEvaluator(pluginPath, typeName, keyValueStore); } public static Assembly LoadPlugin(string absolutePath) @@ -26,10 +27,10 @@ public static Assembly LoadPlugin(string absolutePath) return loadContext.LoadFromAssemblyName(new AssemblyName(Path.GetFileNameWithoutExtension(absolutePath))); } - public static IPluginActionEvaluator CreateActionEvaluator(Assembly assembly, string typeName, string stateStorePath) + public static IPluginActionEvaluator CreateActionEvaluator(Assembly assembly, string typeName, IPluginKeyValueStore keyValueStore) { if (assembly.GetType(typeName) is Type type && - Activator.CreateInstance(type, args: stateStorePath) as IPluginActionEvaluator + Activator.CreateInstance(type, args: new WrappedKeyValueStore(keyValueStore)) as IPluginActionEvaluator is IPluginActionEvaluator pluginActionEvaluator) { return pluginActionEvaluator; @@ -38,8 +39,8 @@ public static IPluginActionEvaluator CreateActionEvaluator(Assembly assembly, st throw new NullReferenceException("PluginActionEvaluator not found with given parameters"); } - public static IPluginActionEvaluator CreateActionEvaluator(string pluginPath, string typeName, string stateStorePath) - => CreateActionEvaluator(LoadPlugin(pluginPath), typeName, stateStorePath); + public static IPluginActionEvaluator CreateActionEvaluator(string pluginPath, string typeName, IKeyValueStore keyValueStore) + => CreateActionEvaluator(LoadPlugin(pluginPath), typeName, new PluginKeyValueStore(keyValueStore)); public IReadOnlyList Evaluate(IPreEvaluationBlock block, HashDigest? baseStateRootHash) => _pluginActionEvaluator.Evaluate( diff --git a/Libplanet.Headless/Hosting/LibplanetNodeService.cs b/Libplanet.Headless/Hosting/LibplanetNodeService.cs index 11ce03a43..3f093d671 100644 --- a/Libplanet.Headless/Hosting/LibplanetNodeService.cs +++ b/Libplanet.Headless/Hosting/LibplanetNodeService.cs @@ -96,7 +96,7 @@ public LibplanetNodeService( var iceServers = Properties.IceServers; - (Store, StateStore) = LoadStore( + (Store, StateStore, IKeyValueStore keyValueStore) = LoadStore( Properties.StorePath, Properties.StoreType, Properties.StoreStatesCacheSize); @@ -125,7 +125,7 @@ IActionEvaluator BuildActionEvaluator(IActionEvaluatorConfiguration actionEvalua new PluggedActionEvaluator( pluginActionEvaluatorConfiguration.PluginPath, pluginActionEvaluatorConfiguration.TypeName, - Path.Combine(Properties.StorePath, "states")), + keyValueStore), RemoteActionEvaluatorConfiguration remoteActionEvaluatorConfiguration => new RemoteActionEvaluator( new Uri(remoteActionEvaluatorConfiguration.StateServiceEndpoint)), @@ -307,7 +307,7 @@ public override async Task StopAsync(CancellationToken cancellationToken) } } - protected (IStore, IStateStore) LoadStore(string path, string type, int statesCacheSize) + protected (IStore, IStateStore, IKeyValueStore) LoadStore(string path, string type, int statesCacheSize) { IStore store = null; if (type == "rocksdb") @@ -351,7 +351,7 @@ public override async Task StopAsync(CancellationToken cancellationToken) IKeyValueStore stateKeyValueStore = new RocksDBKeyValueStore(Path.Combine(path, "states")); IStateStore stateStore = new TrieStateStore(stateKeyValueStore); - return (store, stateStore); + return (store, stateStore, stateKeyValueStore); } private async Task StartSwarm(bool preload, CancellationToken cancellationToken) From cf6284ca54f1231237b0ac5ea0c2c4da13861a68 Mon Sep 17 00:00:00 2001 From: Suho Lee Date: Thu, 30 Nov 2023 16:53:46 +0900 Subject: [PATCH 16/21] fix: lint --- .../WrappedKeyValueStore.cs | 2 +- Libplanet.Headless/Hosting/LibplanetNodeService.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Libplanet.Extensions.PluggedActionEvaluator/WrappedKeyValueStore.cs b/Libplanet.Extensions.PluggedActionEvaluator/WrappedKeyValueStore.cs index 19e7042f1..dbe02da71 100644 --- a/Libplanet.Extensions.PluggedActionEvaluator/WrappedKeyValueStore.cs +++ b/Libplanet.Extensions.PluggedActionEvaluator/WrappedKeyValueStore.cs @@ -3,7 +3,7 @@ namespace Libplanet.Extensions.PluggedActionEvaluator { - public class WrappedKeyValueStore: IKeyValueStore + public class WrappedKeyValueStore : IKeyValueStore { private readonly IPluginKeyValueStore _pluginKeyValueStore; diff --git a/Libplanet.Headless/Hosting/LibplanetNodeService.cs b/Libplanet.Headless/Hosting/LibplanetNodeService.cs index 3f093d671..3dc489aa9 100644 --- a/Libplanet.Headless/Hosting/LibplanetNodeService.cs +++ b/Libplanet.Headless/Hosting/LibplanetNodeService.cs @@ -126,7 +126,7 @@ IActionEvaluator BuildActionEvaluator(IActionEvaluatorConfiguration actionEvalua pluginActionEvaluatorConfiguration.PluginPath, pluginActionEvaluatorConfiguration.TypeName, keyValueStore), - RemoteActionEvaluatorConfiguration remoteActionEvaluatorConfiguration => + RemoteActionEvaluatorConfiguration remoteActionEvaluatorConfiguration => new RemoteActionEvaluator( new Uri(remoteActionEvaluatorConfiguration.StateServiceEndpoint)), DefaultActionEvaluatorConfiguration _ => From 8ce33136a0516f0fd97ef382e17e4680b7b72e35 Mon Sep 17 00:00:00 2001 From: Suho Lee Date: Thu, 30 Nov 2023 18:27:21 +0900 Subject: [PATCH 17/21] fix: config for plugin aev --- .../Hosting/PluggedActionEvaluatorConfiguration.cs | 2 +- NineChronicles.Headless.Executable/Program.cs | 4 ++++ NineChronicles.Headless.Executable/appsettings-schema.json | 5 +---- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/Libplanet.Headless/Hosting/PluggedActionEvaluatorConfiguration.cs b/Libplanet.Headless/Hosting/PluggedActionEvaluatorConfiguration.cs index a58c540ac..cca6d3a4d 100644 --- a/Libplanet.Headless/Hosting/PluggedActionEvaluatorConfiguration.cs +++ b/Libplanet.Headless/Hosting/PluggedActionEvaluatorConfiguration.cs @@ -6,5 +6,5 @@ public class PluggedActionEvaluatorConfiguration : IActionEvaluatorConfiguration public string PluginPath { get; init; } - public string TypeName { get; init; } + public string TypeName => "Lib9c.Plugin.PluginActionEvaluator"; } diff --git a/NineChronicles.Headless.Executable/Program.cs b/NineChronicles.Headless.Executable/Program.cs index d3ada1c07..2dc68caef 100644 --- a/NineChronicles.Headless.Executable/Program.cs +++ b/NineChronicles.Headless.Executable/Program.cs @@ -275,6 +275,10 @@ public async Task Run( return (range, actionEvaluatorConfiguration); }).ToImmutableArray() }, + ActionEvaluatorType.PluggedActionEvaluator => new PluggedActionEvaluatorConfiguration + { + PluginPath = configuration.GetValue("PluginPath"), + }, _ => throw new InvalidOperationException("Unexpected type."), }; } diff --git a/NineChronicles.Headless.Executable/appsettings-schema.json b/NineChronicles.Headless.Executable/appsettings-schema.json index 83a714594..0f6fb08d6 100644 --- a/NineChronicles.Headless.Executable/appsettings-schema.json +++ b/NineChronicles.Headless.Executable/appsettings-schema.json @@ -226,14 +226,11 @@ "type": { "const": "PluggedActionEvaluator" }, - "pluginTypeName": { - "type": "string" - }, "pluginPath": { "type": "string" } }, - "required": [ "pluginPath", "TypeName" ], + "required": [ "pluginPath" ], "additionalProperties": false }, { From 15bf874697e69559fd060239dda5e1db6d3b3827 Mon Sep 17 00:00:00 2001 From: Suho Lee Date: Thu, 30 Nov 2023 21:23:30 +0900 Subject: [PATCH 18/21] feat: resolve plugin path --- .../Hosting/LibplanetNodeService.cs | 32 +++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/Libplanet.Headless/Hosting/LibplanetNodeService.cs b/Libplanet.Headless/Hosting/LibplanetNodeService.cs index 3dc489aa9..a77e4a58f 100644 --- a/Libplanet.Headless/Hosting/LibplanetNodeService.cs +++ b/Libplanet.Headless/Hosting/LibplanetNodeService.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Collections.Immutable; using System.IO; +using System.IO.Compression; using System.Linq; using System.Net.Http; using System.Threading; @@ -117,13 +118,14 @@ public LibplanetNodeService( } var blockChainStates = new BlockChainStates(Store, StateStore); + IActionEvaluator BuildActionEvaluator(IActionEvaluatorConfiguration actionEvaluatorConfiguration) { return actionEvaluatorConfiguration switch { PluggedActionEvaluatorConfiguration pluginActionEvaluatorConfiguration => new PluggedActionEvaluator( - pluginActionEvaluatorConfiguration.PluginPath, + ResolvePluginPath(pluginActionEvaluatorConfiguration.PluginPath), pluginActionEvaluatorConfiguration.TypeName, keyValueStore), RemoteActionEvaluatorConfiguration remoteActionEvaluatorConfiguration => @@ -573,7 +575,7 @@ protected async Task CheckPeerTable(CancellationToken cancellationToken = defaul if (grace == count) { var message = "No any peers are connected even seed peers were given. " + - $"(grace: {grace}"; + $"(grace: {grace}"; Log.Error(message); // _exceptionHandlerAction(RPCException.NetworkException, message); Properties.NodeExceptionOccurred(NodeExceptionType.NoAnyPeer, message); @@ -634,6 +636,32 @@ public override void Dispose() Log.Debug("Store disposed."); } + private string ResolvePluginPath(string path) => + Uri.IsWellFormedUriString(path, UriKind.Absolute) + ? DownloadPlugin(path).Result + : path; + + private async Task DownloadPlugin(string url) + { + var path = Path.Combine(Environment.CurrentDirectory, "plugins"); + Directory.CreateDirectory(path); + var hashed = url.GetHashCode().ToString(); + var logger = Log.ForContext("LibplanetNodeService", hashed); + using var httpClient = new HttpClient(); + var downloadPath = Path.Join(path, hashed + ".zip"); + var extractPath = Path.Join(path, hashed); + logger.Debug("Downloading..."); + await File.WriteAllBytesAsync( + downloadPath, + await httpClient.GetByteArrayAsync(url, SwarmCancellationToken), + SwarmCancellationToken); + logger.Debug("Finished downloading."); + logger.Debug("Extracting..."); + ZipFile.ExtractToDirectory(downloadPath, extractPath); + logger.Debug("Finished extracting."); + return Path.Combine(extractPath, "Lib9c.Plugin.dll"); + } + // FIXME: Request libplanet provide default implementation. private sealed class ActionTypeLoaderContext : IActionTypeLoaderContext { From 26cf7884ab5b916f0de564e19ebd3b934675d944 Mon Sep 17 00:00:00 2001 From: Suho Lee Date: Fri, 1 Dec 2023 13:54:25 +0900 Subject: [PATCH 19/21] fix: do not wrapping twice --- .../PluggedActionEvaluator.cs | 2 +- .../WrappedKeyValueStore.cs | 44 ------------------- 2 files changed, 1 insertion(+), 45 deletions(-) delete mode 100644 Libplanet.Extensions.PluggedActionEvaluator/WrappedKeyValueStore.cs diff --git a/Libplanet.Extensions.PluggedActionEvaluator/PluggedActionEvaluator.cs b/Libplanet.Extensions.PluggedActionEvaluator/PluggedActionEvaluator.cs index adf9fce96..c2ee18264 100644 --- a/Libplanet.Extensions.PluggedActionEvaluator/PluggedActionEvaluator.cs +++ b/Libplanet.Extensions.PluggedActionEvaluator/PluggedActionEvaluator.cs @@ -30,7 +30,7 @@ public static Assembly LoadPlugin(string absolutePath) public static IPluginActionEvaluator CreateActionEvaluator(Assembly assembly, string typeName, IPluginKeyValueStore keyValueStore) { if (assembly.GetType(typeName) is Type type && - Activator.CreateInstance(type, args: new WrappedKeyValueStore(keyValueStore)) as IPluginActionEvaluator + Activator.CreateInstance(type, args: keyValueStore) as IPluginActionEvaluator is IPluginActionEvaluator pluginActionEvaluator) { return pluginActionEvaluator; diff --git a/Libplanet.Extensions.PluggedActionEvaluator/WrappedKeyValueStore.cs b/Libplanet.Extensions.PluggedActionEvaluator/WrappedKeyValueStore.cs deleted file mode 100644 index dbe02da71..000000000 --- a/Libplanet.Extensions.PluggedActionEvaluator/WrappedKeyValueStore.cs +++ /dev/null @@ -1,44 +0,0 @@ -using Lib9c.Plugin.Shared; -using Libplanet.Store.Trie; - -namespace Libplanet.Extensions.PluggedActionEvaluator -{ - public class WrappedKeyValueStore : IKeyValueStore - { - private readonly IPluginKeyValueStore _pluginKeyValueStore; - - public WrappedKeyValueStore(IPluginKeyValueStore pluginKeyValueStore) - { - _pluginKeyValueStore = pluginKeyValueStore; - } - - public void Dispose() - { - _pluginKeyValueStore.Dispose(); - } - - public byte[] Get(in KeyBytes key) => - _pluginKeyValueStore.Get(key.ByteArray); - - public void Set(in KeyBytes key, byte[] value) => - _pluginKeyValueStore.Set(key.ByteArray, value); - - public void Set(IDictionary values) => - _pluginKeyValueStore.Set( - values.ToDictionary(kv => - kv.Key.ByteArray, kv => kv.Value)); - - public void Delete(in KeyBytes key) => - _pluginKeyValueStore.Delete(key.ByteArray); - - public void Delete(IEnumerable keys) => - _pluginKeyValueStore.Delete( - keys.Select(key => key.ByteArray)); - - public bool Exists(in KeyBytes key) => - _pluginKeyValueStore.Exists(key.ByteArray); - - public IEnumerable ListKeys() => - _pluginKeyValueStore.ListKeys().Select(key => new KeyBytes(key)); - } -} From 338d1e89c771a9e69c1c5fdd835e08e665450776 Mon Sep 17 00:00:00 2001 From: Suho Lee Date: Mon, 4 Dec 2023 18:46:13 +0900 Subject: [PATCH 20/21] bump: lib9c --- Lib9c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib9c b/Lib9c index 7f504d197..bce075675 160000 --- a/Lib9c +++ b/Lib9c @@ -1 +1 @@ -Subproject commit 7f504d197f0f159275f3901c820de4b087260445 +Subproject commit bce075675ba388b00c092ec321ee8e5f2f71bd3c From 5d92b177f45281237a4b40cb68e32437598ba2db Mon Sep 17 00:00:00 2001 From: Suho Lee Date: Mon, 4 Dec 2023 18:48:41 +0900 Subject: [PATCH 21/21] chore: add comment on the appsettings schema --- NineChronicles.Headless.Executable/appsettings-schema.json | 1 + 1 file changed, 1 insertion(+) diff --git a/NineChronicles.Headless.Executable/appsettings-schema.json b/NineChronicles.Headless.Executable/appsettings-schema.json index 0f6fb08d6..55a6e225a 100644 --- a/NineChronicles.Headless.Executable/appsettings-schema.json +++ b/NineChronicles.Headless.Executable/appsettings-schema.json @@ -227,6 +227,7 @@ "const": "PluggedActionEvaluator" }, "pluginPath": { + "$comment": "Local path or URI. If it is URI, download it under ./plugin", "type": "string" } },